vue基础
zpyu521 人气:0vue
一 、简介
是什么
- 是一个专注于视图的渐进式前端框架,如果需要其他功能可以使用它的拓展库,Vue 的核心库只关注视图层。
优缺点
- 可以和其他语言共存,在一个app或者一个页面视图中,可以使用其他的语言开发,不会影响,因为他是组件化的
二、命令-生命周期函数
命令
order | 语法糖 | 作用 |
---|---|---|
v-bind=attr | :attr | 将数据和标签帮顶起来 |
v-on:action=actionName | @action=actionName | 绑定事件,事件不需要传参数可以省略() |
{{expression} | 从vue对象中取值 | |
v-if="boolean" | if | |
v-else-if="boolean" | else if | |
v-else | else | |
key | 作为一种标签的身份标识 | |
v-show="boolean" | 和v-if的区别是,它是display=none标签还在 | |
v-for="(item, index) in" | for循环 | |
:class="{className:boolean}" | 也可以用数组 | |
v-model="entity" | 双向绑定表单,实际上是两个指令结合v-bind:value和v-on:input | |
生命周期函数
created():当组件创建成功时
export default { name: "Home", created() { console.log("Home组件被创建成功"); } };
mounted(): 当组件被挂载到dom上
export default { name: "Home", created() { console.log("Home组件被创建成功"); }, mounted(){ console.log("组件被挂载成功") } };
updated(): 当组件中发生变化时
export default { name: "Home", created() { console.log("Home组件被创建成功"); }, mounted(){ console.log("组件被挂载成功") }, updated(){ console.log("组件中发生改变时"); } };
事件传参问题
- 绑定事件时没写(),但是需要传参,参数为undefined
- 绑定事件写了(),需要传参但是没有传,vue会默认传绑定的event对象给第一个参数,第二个就是undefined
- 如果需要传event对象,只写event会被vue解析如果没又在vue中定义会报错,需要写成$event
事件的冒泡、捕获、其他属性
- 冒泡:@click="action(123)" =>@click.stop="action(123)" , 解决冒泡
- 捕获:@click="action(123)" =>@click.prevent="action(123)"
- 键盘:@keyUp【常用】|keyDown.enter="action",监听enter键
- 自定义组件的根元素的事件:@click.native
- 只监听一次: @click.once
标签值被复用了
需要给标签添加一个key的属性就可以了,是因为虚拟dom的问题
v-for
- 一般v-for中要指定:key=“一般是一个唯一的东西” 要和每一个标签对应起来,虚拟dom进行diff算法的时候会复用这个标签
<!-- 当items是数组 -->
<ul v-for="(item, index) in items" :key="item">
<li></li>
</ul>
<!-- 当items是对象,默认是取value -->
<ul v-for="value in obj" :key="value">
<li></li>
</ul>
<!-- 当items是对象,默认是取value,key,index -->
<ul v-for="(value,key,index) in obj" >
<li></li>
</ul>
- 支持响应式的方法
- pop()删除最后一个元素
- push(a,b...)追加一【多】个元素
- shift()删除第一个元素
- unshift(a,b...)添加【多】元素到第一个的位置
- sort([compare])
- reverse()
- splice(起始位置,删除几个【只填第一个参数就是从第一个位置删除到完】,从第一个参数位置添加【可以多个】的元素)
- Vue.set(src,index,newValue) 修改src 中index位置值
- 不支持响应式的方法
- items[index]=""
过滤器
在vue的options中定义filters:{run :function(pram),调用 param|run
三、v-model修饰符
<input type="text" v-model="message" />{{message}}
v-model.lazy
- v-model 会和标签双向绑定,但是编辑时,时时刻刻都在同步,资源消耗没必要,v-model.lazy会监听enter 或者手标失去焦点的时候才进行数据同步
<!-- 失去焦点或者enter才会更新值--><input type="text" v-model.lazy="message">{{message}}
v-model.number
- 将输入框的值转换为number类型,默认是字符串处理所有的键盘录入
v-model.trim
- 将输入框中的左右两边的空格去掉
四、组件化
什么是组件
借鉴了将一个大的问题拆分成一个个的小问题这种思想
- 将一个页面拆分成一个个的小组件,可以递归的拆分
- 每个组件完成自己相关的功能,多个组件共同组成一个页面或者程序
- 复用性:下次需要同样的功能就可以复用
组件的使用
- 创建组件的构造器
- 注册组件
- 使用组件
必须放在vue管理的作用域内,如果是多个标签必须被一个元素包裹,就是有一个唯一的祖先元素
<div id="app">
<cpt></cpt>
<cpt></cpt>
<cpt></cpt>
<cpt></cpt>
</div>
<script>
// 1. 创建组件构造器
const component = Vue.extend({
template: `
<div>
hello
</div>`,
});
// 2. 注册组件 全局组件
Vue.component('cpt', component);
const app = new Vue({
el: "#app",
data: {
message: "hello world"
}
});
</script>
局部组件
<div id="app">11
<cpt></cpt>
<cpt></cpt>
</div>
<div id="app2">22
<cpt></cpt>
</div>
<script>
// 1. 创建组件构造器
const component = Vue.extend({
template: `
<div>
hello
</div>`,
});
//局部组件 只在app中的作用域有效
const app = new Vue({
el: "#app",
data: {
message: "hello world"
},
components: {
cpt: component
}
});
const app2 = new Vue({
el: "#app2",
data: {
message: "hello"
}
});
</script>
父子组件
<div id="app">11
<pt></pt>
<pt></pt>
<pt></pt>
</div>
<script>
/*第1个组件构造器*/
const child = Vue.extend({
template: `
<div>
child
</div>`
});
// 第二创建组件构造器
const parent = Vue.extend({
template: `
<div>
parent
<cd></cd>
</div>`,
components: {
cd: child
}
});
//局部组件 只在app中的作用域有效
const app = new Vue({
el: "#app",
data: {
message: "hello world"
},
components: {
pt: parent
}
});
</script>
组件的传递
组件不会向上级作用域传递,只会向下传递,孙子没有在爷爷的作用域注册的话孙子只能在父亲的作用域使用
组件的语法糖
<div id="app">11
<pt></pt>
<pt></pt>
<pt></pt>
</div>
<script>
//局部组件 只在app中的作用域有效
const app = new Vue({
el: "#app",
data: {
message: "hello world"
},
components: {
pt: {
// 语法糖直接可以放在注册的地方
template: `
<div>
hello
</div>`
}
}
});
</script>
模板的分离
<script src="../../js/vue.js"></script>
<div id="app">11
<pt></pt>
<pt></pt>
<pt></pt>
</div>
<!--<script type="text/x-template" id="pt">
<div>
<div>我是标题</div>
</div>
</script>-->
<template id="pt">
<div>
<div>我是tempalte</div>
</div>
</template>
<script>
//局部组件 只在app中的作用域有效
const app = new Vue({
el: "#app",
data: {
message: "hello world"
},
components: {
pt: {
// 语法糖直接可以放在注册的地方
template: "#pt"
}
}
});
</script>
组件访问数据
- 组件不能访问实例中的数据
- 只能访问自己的数据
- data属性是一个function不是对象,可以返回一个数据对象供它访问
- 组件也有method属性,它的原型实际上是指向vue的实例的
<div id="app">11
<pt></pt>
<pt></pt>
<pt></pt>
</div>
<template id="pt">
<div>
<div>我是{{title}}</div>
</div>
</template>
<script>
//局部组件 只在app中的作用域有效
const app = new Vue({
el: "#app",
data: {
message: "hello world"
},
components: {
pt: {
template: "#pt",
//是一个函数,且只能访问自己的数据
data(){
return {title:"title"};
}
}
}
});
</script>
组件的data必须是函数
- 如果写属性的话,很容易造成多个组件的数据引用指向同一块内存,会相互影响
- 用function的话你只要每次返回一个匿名对象,他是没有公共引用指向的所以不会影响,如果需要的话你自己可以return 一个公用的引用就会相互影响的
- 所以为了避免这种bug,data不是function就会报错
- 必须return 一个对象{}
父子组件通信
父传子
- props属性 : 可以写成数组或者对象,对象可以限制类型,对象更好点,也可以类型写成对象添加更多的限制、给默认值
- 给默认值的时候如果是对象或者是数组,不能直接用{}、[] 需要用工厂来创建
- 自定义validator
- 可以自定义一个类作为类型
<div id="app">
<pt :msg="msg" :title="title"></pt>
</div>
<template id="pt">
<div>
<div>{{title}}</div>
<div>{{msg}}</div>
</div>
</template>
<script>
// 1.注册组件
const pt = {
template:"#pt",
data() {
return {};
},
methods: {},
// props:["title","msg"] 可以写成数组或者对象,对象可以限制类型,对象更好点
props:{
// title:Array,
title:{
type: Array,
default(){
return [];
}
},
//也可以写成对象的添加更多的限制、给默认值
msg:{
type:String,
default:"",
required:true,
//自定义validator 这个待查阅
validator: function (val) {
return val == "hello worl";
}
}
}
}
//局部组件 只在app中的作用域有效
const app = new Vue({
el: "#app",
data: {
msg: "hello world",
title:["aaa","bbb","ccc"]
},
//字面量简写
components:{pt}
});
</script>
子传父|自定义事件
v-on
事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以v-on:myEvent
将会变成v-on:myevent
——导致myEvent
不可能被监听到。- $emit --》this.$emit('myevent')会传递给父组件的监听事件要同名
- 推荐你始终使用 kebab-case 的事件名 my-event
- 子组件尽量和自己的data属性去绑定
<div id="app">
<!-- 不写参数会默认将$emit事件后传的参数【可多个】传出来,写了参数报错-->
<pt @child-click="parentClick"></pt>
</div>
<template id="pt">
<div>
<button v-for="item in categories" @click="btnClick(item)">{{item.name}}</button>
</div>
</template>
<script>
// 1.注册组件
const pt = {
template: "#pt",
data() {
return {
categories: [
{id: "aaa", name: "aaa"},
{id: "bbb", name: "bbb"},
{id: "ccc", name: "ccc"},
{id: "ddd", name: "ddd"}
]
};
},
methods: {
btnClick(ite) {
// js中这样写不能驼峰,vue可以
this.$emit('child-click', ite,1);
}
}
};
//局部组件 只在app中的作用域有效
const app = new Vue({
el: "#app",
data: {
msg: "hello world",
title: ["aaa", "bbb", "ccc"]
},
components: {pt},
methods: {
parentClick(obj,a) {
console.log(obj,a);
}
}
});
</script>
练习
- num1、num2从父组件传递过来
- 修改num1,dnum1也变,同时传dnum1给父组件,父组件改变num1,也改变了prop1
- dnum2一直是dnum1的1%
<!--1. num1、num2从父组件传递过来
2. 修改num1,dnum1也变,同时传dnum1给父组件,父组件改变num1,也改变了prop1
3. dnum2一直是dnum1的1%-->
<div id="app">
<pt :cnum1="num1" :cnum2="num2"
@change1="cc1"
@change2="cc2"
></pt>
</div>
<template id="pt">
<div>
<p>props:{{cnum1}}</p>
<p>data:{{dnum1}}</p>
cnum1<input type="text" :value="dnum1" @input="changeProp1"><br>
<p>props:{{cnum2}}</p>
<p>data:{{dnum2}}</p>
cnum2<input type="text" :value="dnum2" @input="changeProp2">
</div>
</template>
<script>
//局部组件 只在app中的作用域有效
const app = new Vue({
el: "#app",
data: {
num1: 1,
num2: 2
},
methods: {
cc1(eve1) {
this.num1 = eve1;
},
cc2(eve2) {
this.num2 = eve2;
}
},
components: {
pt: {
template: "#pt",
props: {
cnum1: {
type: Number,
default: 3
},
cnum2: {
type: Number,
default: 4
}
},
data() {
return {
dnum1: this.cnum1,
dnum2: this.cnum2,
};
}, methods: {
changeProp1(event1) {
this.dnum1 = event1.target.value;
console.log(this.dnum1)
if (this.dnum1) {
this.dnum1 = parseInt(this.dnum1)
this.dnum2 = this.dnum1 / 100;
this.$emit('change1', this.dnum1);
} else {
this.dnum2 = "";
}
},
changeProp2(event2) {
this.dnum2 = event2.target.value;
this.$emit('change2', parseInt(this.dnum2));
}
}
}
}
});
</script>
watch
- 监听对象不能直接监听,可以用computed代替
<script src="../../js/vue.js"></script>
<div id="app">
{{message}}
<input type="text" v-model="message">
{{demo.name}}
<input type="text" v-model="demo.name">
</div>
<template id="cd">
<div>
aaaaa
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello world",
demo: {
name: "nameObj"
}
},
computed:{
demoName(){
return this.demo.name;
}
},
watch: {
message(newVal, oldVal) {
console.log(newVal, oldVal);
},
//不能直接监听对象
// demo(val) {
// console.log(val);
// }
demoName(val) {
console.log(val);
}
},
components: {
cd: {
template: "#cd"
}
}
});
</script>
- 如果是键的路径需要用引号包裹
- 也可以外部调用
<div id="app">
{{demo1.name}}
<input type="text" v-model="demo1.name">
{{demo.name}}
<input type="text" v-model="demo.name">
<input type="text" v-model="demo2">
</div>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello world",
demo: {
name: "nameObj"
},
demo1: {
name: "nameObj"
},
demo2:"qweqw"
},
computed: {
demoName() {
return this.demo.name;
}
},
watch: {
//如果是键的路径需要用引号包裹
"demo.name": function (val) {
console.log(val);
},
// childrens: {
// handler:function(val){
// console.log(val.name);
// },
// deep: true
// },
// "childrens.name":function (val) {
// console.log(val);
// }
}
});
//外部调用
app.$watch("demo2",function (val) {
console.log(val)
})
</script>
访问子组件实例 $children和$refs
- 一般不会用$children来取子组件
- $refs.refName|['refName']
- 如果多个相同的引用会取最后一个
- 如果绑定的是一个普通标签拿到的就是一个dom对象
<div id="app">
<tmp ref="a"></tmp>
<tmp ref="a"></tmp>
<tmp ref="b"></tmp>
<button @click="btnClick">打印子组件</button>
</div>
<template id="tmp">
<div>
<p>哈哈哈</p>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello world"
},
methods:{
btnClick(){
//1. 一般不会用$children来取子组件
// console.log("第一个子组件:",this.$children[0]);
// console.log("所有子组件:",this.$children);
// 2.$refs.refName|['refName']
console.log("所有组件有ref属性的组件:",this.$refs);
//如果多个相同的引用会取最后一个
console.log("取得固定的ref的元素:",this.$refs["a"]);
console.log("取得固定的ref的元素:",this.$refs.b);
}
},
components: {
tmp: {
template: "#tmp"
}
},
});
</script>
访问父组件实例
- 不建议使用this.$parent,会让组件的耦合增强不够独立
- 祖先组件this.$root
<div id="app">
<tmp></tmp>
</div>
<template id="tmp">
<div>
<p>哈哈哈</p>
<button @click="btnClick">打印父组件</button>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello world"
},
components: {
tmp: {
template: "#tmp",
methods: {
btnClick() {
//1. 不建议使用,会让组件的耦合增强不够独立
console.log("打印直系父组件:", this.$parent);
//祖先组件
console.log("打印root组件:", this.$root);
}
}
},
},
});
插槽slot
- 拓展组件像回调函数一样,usb接口一样
- 插槽的基本使用
- 插槽的默认值
<!--1. 插槽的基本使用 <slot></slot>-->
<!--2. 插槽的默认值 <slot>默认值</slot>-->
<div id="app">
<tmp></tmp><br>
<tmp></tmp><br>
<tmp></tmp><br>
<tmp><div>我是插槽</div></tmp>
<tmp><i>我是插槽i</i></tmp>
</div>
<template id="tmp">
<div>
<p>哈哈哈</p>
<slot><p>我是默认值*******</p></slot>
<p>娃娃</p>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello world"
},
components: {
tmp: {
template: "#tmp"
},
}
});
</script>
具名插槽
<div id="app">
<tmp ><a slot="right" href="#">我替换右边</a></tmp><br>
<tmp ><a slot="left" href="#">我替换左边</a></tmp><br>
<tmp><a href="#">我替换没名字的</a></tmp><br>
</div>
<template id="tmp">
<div>
<slot name="left"><p>我是默认值left</p></slot>
<slot name="center"><p>我是默认值center</p></slot>
<slot name="right"><p>我是默认值right</p></slot>
<slot><p>我是默认值没有名字</p></slot>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello world"
},
components: {
tmp: {
template: "#tmp"
},
}
});
编译作用域
- 始终使用自己组件中的变量
<div id="app">
<!-- 在谁的作用域用谁的变量-->
<cp v-show="isShow"></cp>
</div>
<template id="cp">
<div v-show="isShow"><!-- div父元素初始化的时候不受影响 -->
<a href="">aaa</a>
<button v-show="isShow">按钮</button>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello world",
isShow: true
},
components: {
cp: {
template: "#cp",
data() {
return {
isShow: false
};
}
}
}
});
</script>
作用域插槽
- 父组件想要替换子组件的插槽的数据,数据的具体值还是由子组件来决定
- slot-scope="slotData",类似于该组件的对象,2.5之前要用template标签
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="../../js/vue.js"></script>
<div id="app">
<cp>
<!-- slotData:类似于该组件的对象,2.5之前要用template-->
<template slot-scope="slotData">
<!-- 取得绑定在组件中的数据-->
<span v-for="item in slotData.datas">{{item}}-</span>
</template>
</cp>
<cp>
<template slot-scope="slotData">
<!-- join方法将数组拼接成字符串-->
<span>{{slotData.datas.join(' * ')}}</span>
</template>
</cp>
</div>
<template id="cp">
<div>
<!-- 作为传递的数据-->
<slot :datas="languages">
<ul>
<li v-for="item in languages">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {
message: "hello world",
},
components: {
cp: {
template: "#cp",
data() {
return {
languages: ['java', 'javascript', 'css', 'html', 'vb', 'python']
};
}
}
}
});
</script>
</body>
</html>
五、es6模块化
为什么有模块化
- js是按顺序加载的,所以一般相互依的js是具有强制性的
- 多个js文件定义的引用会污染全局变量,多人协作开发可能会有冲突
- 可以用闭包
闭包解决多人协作开发
- 只需要写好自己的模块化的命名,就可以很好的避免冲突了,相当于把所有的容错点都聚焦在一个点上,犯错的机会就少了,
- 但是代码的复用性还是很差
// ;是为了防止其他的导入js相互影响
;var xm01 = (function xiaoming01() {
return {
aa:"asdas",
flag: true
};
}())
//js文件2
;(function () {
if (xm01.flag) {
alert("xm01.flag:" + xm01.flag);
}
}());
六、webpack
webpack起步
入口js
//commonjs规范 const {add} = require('./mathUtil.js'); console.log(add(1,3)); //es6规范 import {result} from "./es6.js"; console.log(result);
es6规范
const result = 45456;
export {result};
- common规范
function add(a, b) {
return a + b;
}
module.exports = {add};
webpack配置
- 导出的时候es6和commonjs不能在一个模块中混用
- 配置webpack.config.js:要使用commonjs规范
//node的包里面的path模块,用来拼接绝对路径
const path = require('path');
//这里要用commonjs导出,不能用es6
module.exports = {
entry: './src/main.js',
ouput: {
//必须使用绝对路径
path: path.resolve(__dirname,'dist'),
filename: 'bundle.js'
}
};
- package配置:json不能有注释
{
"name": "meetpackage",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
//npm run build 会在这个配置文件中找webpack命令,这个使用的是本地的命令,
//不是全局的webpack,本地是针对于你的这个开发项目
"build":"webpack"
},
"author": "",
//开发的依赖
"devDependencies": {
"webpack": "^3.6.0"
},
//开源才需要这个,json中不能注释
"license": "ISC"
}
- 本地安装:开发时依赖 npm install webpack@3.6.0 --save-dev
- 终端terminal里敲的命令都是全局的
为什么使用--save-dev而不是--save?
-
-save 会把依赖包名称添加到 package.json 文件 dependencies 下;
-
-save-dev 则添加到 package.json 文件 devDependencies 键下;
webpack-loader
- 官网可以找到对应的loader安装
- 例:npm install style-loader[@version] --save -dev[表示开发环境用]
- npm install babel-loader@7 babel-core babel-preset-es2015 --save-dev es6转es5
//node的包里面的path模块,用来拼接绝对路径
const path = require('path');
//这里要用commonjs导出,不能用es6
module.exports = {
entry: './src/main.js',
output: {
//必须使用绝对路径
path: path.resolve(__dirname,'dist'),
filename: 'bundle.js',
//为所有的url相关的添加路径
publicPath:'dist/'
},
module:{
rules: [
{
test: /\.css$/,
// style-loader将模块的导出作为样式添加到 DOM 中
// loader解析 CSS 文件后,使用 import 加载,并且返回 CSS 代码
// 从右到左的顺序加载
use: [ 'style-loader', 'css-loader' ]
},
// {
// test: /\.(png|jpg|gif)$/,
// use: [
// {
// loader: 'url-loader',
// options: {
// //限制图片大小,大于limit会找file-loader
// limit: 9999
// }
// }
// ]
// },
// 在使用webpack进行打包时,对图片路径的处理方法常用的有两种,一种是file-loader,
// 一种是url-loader,当我们使用其中一种是,请把另一种删掉,不然会出现图片无法正常显示的问题
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
//name是文件名,hash取8位,ext是拓展名
name:'img/[name].[hash:8].[ext]'
}
}
]
},
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['es2015']
}
}
}
]
}
};
webpack-vue
npm install vue -save
不写路径默认从node_modules引入 import Vue from 'vue'
runtime-only:是运行的时候代码不能包含任意一个template标签
runtime-compiler:代码中可以有template标签
- 解决3.4碰到的问题
module:{ resolve:{ alias:{ // vue$正则,表示导入的时候会检测vue指向的文件夹,如果这里不指定,会去找默认的runtime-only 'vue$':'vuehttps://img.qb5200.com/download-x/dist/vue.esm.js' } }
//使用vue
import Vue from 'vue';
const App = {
template: `
<h2>{{msg}}</h2>
`,
data() {
return {
msg: 'hello world'
};
}
};
new Vue({
el: '#app',
// template和el关系是,这里的template会替换el的标签
template: `<App/>`,
components: {
App
}
});
将组件的代码提出去
- 新建一个.vue文件
<template>
<h2>{{msg}}</h2>
<span class="title">{{tit}}</span>
</template>
<script>
export default {
name: "App",
data() {
return {
msg: 'hello world',
tit:'title'
};
}
}
</script>
<style scoped>
.title{
color: red;
}
</style>
- npm install vue-loader vue-template-compiler --save -dev
- 会出现版本过高的问题 安装一个低版本的
- 编辑package.json中的版本号,会根据你的大版本找一个合适的,必须重新npm install
webpack-plugin
- 安装打包静态文件:npm install --save-dev html-webpack-plugin
- 压缩js文件替换变量为更简单的:npm install uglifyjs-webpack-plugin@1.1.1 --save -dev 指定的vueCli 2
webpack-dev-server
全局安装:可以不用
npm install webpack-dev-server -g
开发环境:
npm install webpack-dev-server -save -dev
配置参数:
--content-base //设定webpack-dev-server的director根目录。如果不进行设定的话,默认是在当前目录下。 --quiet: //控制台中不输出打包的信息,开发中一般设置为false,进行 打印,这样查看错误比较方面 --no-info: // 不显示任何信息 --colors: //对信息进行颜色输出 --no-colors: //对信息不进行颜色输出 --compress: //开启gzip压缩 --host <hostname/ip>: //设置ip --port <number>: //设置端口号,默认是:8080 --inline: //webpack-dev-server会在你的webpack.config.js的入口配置文件中再添加一个入口, --hot: //开发热替换 --open: //启动命令,自动打开浏览器 --history-api-fallback: //查看历史url
两种方式:
- 直接scripts中使用:"dev": "webpack-dev-server --contentBase src --port 80 --hot --colors"
- 配置文件:
plugins: [ new webpack.BannerPlugin('最终版权是小明'), //打包静态资源,并且指定模板 new htmlWebpackPlugin({ template:`index.html` }), //压缩js new UglifyJsWebpackPlugin(), //热加载,不会全部加载,只加载改动的地方,配置了hot就需要配置,直接在命令中使用--hot就不需要配置这个插件 // new webpack.HotModuleReplacementPlugin() ], // devServer: { // contentBase: 'src', // port: 80, // hot:true // },
报错可能是版本问题
webpack.config.js配置文件
//node的包里面的path模块,用来拼接绝对路径
const path = require('path');
const webpack = require('webpack');
const htmlWebpackPlugin = require('html-webpack-plugin');
const UglifyJsWebpackPlugin = require('uglifyjs-webpack-plugin');
//这里要用commonjs导出,不能用es6
module.exports = {
entry: './src/main.js',
output: {
//必须使用绝对路径
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
//为所有的url相关的添加路径
// publicPath: 'dist/'
},
module: {
rules: [
{
test: /\.css$/,
// style-loader将模块的导出作为样式添加到 DOM 中
// loader解析 CSS 文件后,使用 import 加载,并且返回 CSS 代码
// 从右到左的顺序加载
use: ['style-loader', 'css-loader']
},
// {
// test: /\.(png|jpg|gif)$/,
// use: [
// {
// loader: 'url-loader',
// options: {
// //限制图片大小,大于limit会找file-loader
// limit: 9999
// }
// }
// ]
// },
// 在使用webpack进行打包时,对图片路径的处理方法常用的有两种,一种是file-loader,
// 一种是url-loader,当我们使用其中一种是,请把另一种删掉,不然会出现图片无法正常显示的问题
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
//name是文件名,hash取8位,ext是拓展名
name: 'img/[name].[hash:8].[ext]'
}
}
]
},
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['es2015']
}
}
},
{
test: /\.vue$/,
use: {
loader: 'vue-loader'
}
}
]
},
resolve: {
// 这写拓展名可以省略
extensions: ['.css', '.js', '.vue'],
alias: {
// vue$正则,表示导入的时候会检测vue指向的文件夹,如果这里不指定,会去找默认的runtime-only
'vue$': 'vuehttps://img.qb5200.com/download-x/dist/vue.esm.js'
}
},
plugins: [
new webpack.BannerPlugin('最终版权是小明'),
//打包静态资源,并且指定模板
new htmlWebpackPlugin({
template:`index.html`
}),
//压缩js
new UglifyJsWebpackPlugin(),
//热加载,不会全部加载,只加载改动的地方,配置了hot就需要配置,直接在命令中使用--hot就不需要配置这个插件
// new webpack.HotModuleReplacementPlugin()
],
// devServer: {
// contentBase: 'src',
// port: 80,
// hot:true
// },
};
抽取分离配置文件
创建三个配置文件:
- base.config.js : 存放公共的配置
//node的包里面的path模块,用来拼接绝对路径 const path = require('path'); const webpack = require('webpack'); const htmlWebpackPlugin = require('html-webpack-plugin'); //这里要用commonjs导出,不能用es6 module.exports = { entry: './src/main.js', output: { //必须使用绝对路径 path: path.resolve(__dirname, '..https://img.qb5200.com/download-x/dist'), filename: 'bundle.js', }, module: { rules: [ { test: /\.css$/, // style-loader将模块的导出作为样式添加到 DOM 中 // loader解析 CSS 文件后,使用 import 加载,并且返回 CSS 代码 // 从右到左的顺序加载 use: ['style-loader', 'css-loader'] }, { test: /\.(png|jpg|gif)$/, use: [ { loader: 'file-loader', options: { //name是文件名,hash取8位,ext是拓展名 name: 'img/[name].[hash:8].[ext]' } } ] }, { test: /\.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['es2015'] } } }, { test: /\.vue$/, use: { loader: 'vue-loader' } } ] }, resolve: { // 这写拓展名可以省略 extensions: ['.css', '.js', '.vue'], alias: { // vue$正则,表示导入的时候会检测vue指向的文件夹,如果这里不指定,会去找默认的runtime-only 'vue$': 'vuehttps://img.qb5200.com/download-x/dist/vue.esm.js' } }, plugins: [ new webpack.BannerPlugin('最终版权是小明'), //打包静态资源,并且指定模板 new htmlWebpackPlugin({ template: `index.html` }) ], };
dev.config.js : 存放开发时配置
const WebpackMerge = require('webpack-merge'); const baseConfig = require('./base.config'); module.exports = WebpackMerge(baseConfig, { devServer: { contentBase: 'src', port: 80, inline: true } });
prod.config.js : 存放生产时配置
const UglifyJsWebpackPlugin = require('uglifyjs-webpack-plugin'); const WebpackMerge = require('webpack-merge'); const baseConfig = require('./base.config'); module.exports = WebpackMerge(baseConfig, { plugins: [ //压缩js new UglifyJsWebpackPlugin() ] });
修改scripts
- 可删除默认的webpack.config.js
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "webpack-dev-server --config ./buildhttps://img.qb5200.com/download-x/dev.config.js", "build": "webpack --config ./build/prod.config.js" },
七、vuecli
介绍:
- vue command interface 命令行界面:使用一些命令搭建项目的基础结构
- 都使用会规范很多配置,易维护,减少出错率
- 依赖 webpack and npm-》npm-》依赖node
安装:
- npm install -g @vue/cli
卸载之前版本
- npm uninstall vue-cli -g / npm uninstall -g @vue/cli
拉取v2的模板
npm install -g @vue/cli-init
v2使用:
- 基于webpack3
创建project
vue init webpack projectName
v3使用:
零配置
隐藏build和config目录,可以在node-modules/@vue/cli-service
要修改配置需要根目录创建一个vue.config.js
module.exports={};
基于webpack4
提供vue ui命令,图形化操作
移除static,新增public目录将index.html移动到下面
创建project
vue create projectName
会默认创建一个.git文件夹
自定义配置:
- 根目录新建 vue.config.js
module.exports = { configureWebpack: { resolve: { // extensions:[], //配置别名 alias: { 'assets': '@/assets', 'components': '@/components', 'network': '@/network', 'common': '@/commom', 'views': '@/views', } } } };
- 配置.editorconfig : 代码格式
root = true [*] charset = utf-8 indent_style = space indent_size = 2 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true
vueUi
vue ui 打开图形管理界面
el
- el:'#app'最后执行的还是$mount('#app')
八、runtime-only 和 runtime-compiler
runtime-only:是运行的时候代码不能包含任意一个template标签
- render(h)- 》 virtual Dom - 》UI真实dom
runtime-compiler:代码中可以有template标签
template加载过程:
template - 》parse - 》ast 抽象语法树 - 》compiler - 》render(h)- 》 virtual Dom - 》UI真实dom
3比4性能更高,代码更少(少6kb)
//runtime-compiler
new Vue({
el: '#app',
components: { App },
template: '<App/>'
})
//runtime-only,这个h是一个createElement('tagName',{attrName:'attrVal'},['innerHtml'])
//在vue中也可以传一个template对象靠vue-template-compiler解析成render(),也可以递归创建
new Vue({
el: '#app',
render: h => h(App)
})
九、路由 vue-router
一般使用vue的插件都要用Vue.use(插件)
- 介绍:
- 互联的网络将信息传输到目标地址的活动
- 路由器提供两种机制:
- 路由:决定数据包从源到目的地的路径
- 将输入端的数据转送到合适的输出端
- 路由表:是一个映射表,决定了数据包的指向
- 后端路由--前端路由
- 后端处理url和页面的映射,jsp是后端渲染,到前端的时候页面就确认好了
- 前端处理url和页面的跳转映射
改编url不刷新页面
- 改location.hash='aaa';
- history:
- 改history.pushState({},'','aaa');类似压栈,history.back()类似弹栈
- 改history.replaceState({},'','aaa'),不能back()
- 改history.go(-1) = history.back(),前进或者后退
- 改history.forword()=history.go(1)
安装路由:npm install vue-router --save 因为生产也需要路由
导入:
- router/index.js
import Vue from 'vue' //1. 导入插件 import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' //2. 使用插件 Vue.use(Router) //3. 创建路由配置 const routes = [ { path: '/', name: 'HelloWorld', component: HelloWorld } ]; //4. 传入路由配置,导出路由对象 export default new Router({ routes })
- main.js
import Vue from 'vue' import App from './App' //只写目录默认会找 index.js import router from './router' Vue.config.productionTip = false new Vue({ el: '#app', router, render: h => h(App) })
router-link
- 替换a标签
<div id="app">
<router-link to="/home">首页</router-link>
<!-- 相当于占位符 -->
<router-view></router-view>
<router-link to="/about">详情</router-link>
</div>
常用属性
tag 、replace
<!-- tag设置替换成什么标签 --> <!-- replace表示禁用了返回前进按钮,是history.replaceState() --> <router-link to="/home" tag='button' replace>首页</router-link>
配置默认的active的样式
.router-link-active{ color: #f00 }
自定义样式:手动一个一个标签的写
<!--active-class 自定义点击后的样式 --> <router-link to="/home" tag='button' replace active-class="active">首页</router-link>
配置全局的active-class
export default new Router({ routes, mode:'history', linkActiveClass:'active' })
默认重定向
const routes = [
{
path:'/',
redirect:'/home'
},
{
path: '/home',
name: 'Home',
component: Home
},
{
path:'/about',
name:'About',
component:About
}
];
设置router的默认方式为history
- 本身默认hash
- history:url不会显示#号
//4. 传入路由配置,导出路由对象
export default new Router({
routes,
mode:'history'
})
手动写路由跳转
- router会给每个组件传$router
<template>
<div id="app">
<button @click="homeClick">首页</button>
<button @click="aboutClick">详细</button>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
methods:{
//router会给每个组件传$router
homeClick(){
// this.$router.push('/home');
this.$router.replace('/home');
},
aboutClick(){
// this.$router.push('/about');
this.$router.replace('/about');
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
动态路由
创建一个vue组件:User.vue
<template> <div> <h2>个人信心</h2> <h3></h3> </div> </template> <script> export default { name:'User', } </script> <style> </style>
配置路由:index.js
import Vue from 'vue' import User from '../components/User.vue' //1. 导入插件 import Router from 'vue-router' //2. 使用插件 Vue.use(Router) //3. 创建路由配置 const routes = [ { path:'/user', component:User } ]; //4. 传入路由配置,导出路由对象 export default new Router({ routes, mode:'history', linkActiveClass:'active' })
加入路由到目标组件:Vue.vue
<template> <div id="app"> <router-link to="/user" replace>用户</router-link> <router-view></router-view> </div> </template> <script> export default { name: 'App', } </script> <style> .active{ color: rgb(209, 15, 25) } </style>
导入组件到入口 : main.js
import Vue from 'vue' import App from './App' //只写目录默认会找 index.js import router from './router' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, render: h => h(App) })
设置动态路由:index.js
import Vue from 'vue' import User from '../components/User.vue' //1. 导入插件 import Router from 'vue-router' //2. 使用插件 Vue.use(Router) //3. 创建路由配置 const routes = [ { path:'/user/:userName', component:User } ]; //4. 传入路由配置,导出路由对象 export default new Router({ routes, mode:'history', linkActiveClass:'active' })
配置页面的url: Vue.vue
<template> <div id="app"> <router-link v-bind:to="'/user/'+userName" replace>用户</router-link> <router-view></router-view> </div> </template> <script> export default { name: 'App', data(){ return { userName:'xiaoming' } } } </script> <style> .active{ color: rgb(209, 15, 25) } </style>
获取动态路由中的参数:User.vue
- $route是当前活跃的路由
<template> <div> <h2>个人信心</h2> <h3>{{userName}}</h3> </div> </template> <script> export default { name:'User', computed:{ userName(){ return this.$route.params.userName; } } } </script> <style> </style>
route 、router 、$router 、$route
- $router:路由组件对象 配置的路由对象
- $route:当前活跃路由
懒加载
如果把所有的js都打包到app中,js将会很大,访问的时候会有等待时间,所以把不同的路由对应的组件分割成不同的代码块,然后当路由被访问的时候加载对应的资源,就更加高效了
import Vue from 'vue' //替换成懒加载 // import Home from '../components/Home.vue' // import About from '../components/About.vue' // import User from '../components/User.vue' //懒加载: const Home = ()=>import('../components/Home.vue') const About = ()=>import('../components/About.vue') const User = ()=>import('../components/User.vue') //1. 导入插件 import Router from 'vue-router' //2. 使用插件 Vue.use(Router) //3. 创建路由配置 const routes = [ { path:'/', redirect:'/home' }, { path: '/home', name: 'Home', component: Home }, { path:'/about', name:'About', component:About }, { path:'/user/:userName', component:User } ]; //4. 传入路由配置,导出路由对象 export default new Router({ routes, mode:'history', linkActiveClass:'active' })
子路由
index.js
import Vue from 'vue' //替换成懒加载 // import Home from '../components/Home.vue' // import About from '../components/About.vue' // import User from '../components/User.vue' //懒加载: const Home = () => import('../components/Home.vue') const About = () => import('../components/About.vue') const User = () => import('../components/User.vue') const HomeChild = () => import ('../components/HomeChild.vue') //1. 导入插件 import Router from 'vue-router' //2. 使用插件 Vue.use(Router) //3. 创建路由配置 const routes = [ { path: '/', redirect: '/home' }, { path: '/home', name: 'Home', component: Home, children: [ { path: '', // redirect:'child' }, { //这里不能同/开头,会自动加上 path: 'child', name: 'HomeChild', component: HomeChild }] }, { path: '/about', name: 'About', component: About }, { path: '/user/:userName', component: User } ]; //4. 传入路由配置,导出路由对象 export default new Router({ routes, mode: 'history', linkActiveClass: 'active' })
Home.vue
<template> <div> <h2>首页11</h2> <router-link to="/home/child">child</router-link> <router-view></router-view> </div> </template> <script> export default { name:'Home' } </script> <style> </style>
传参到另一个组件
profile.vue
<template> <div><span>个人档案</span> <span>{{$route.query}}</span><br> <span>query.name: {{$route.query.name}}</span> </div> </template> <script> export default { name: "Profile" } </script> <style scoped> </style>
配置路由:index.js
const Profile = () => import('../components/Profile') { path: '/profile', component: Profile }
显示位置的路由传参
<template> <div id="app"> <router-link to="/home" tag='button' replace >首页</router-link> <router-link to="/about" replace>详情</router-link> <router-link :to="'/user/'+userName" replace>用户</router-link> <router-link :to="{path:'/profile',query:{name:'lisa',age:18},fragment:'4d5as46s'}" replace>档案</router-link> <!-- <button @click="homeClick">首页</button> <button @click="aboutClick">详细</button> --> <router-view></router-view> </div> </template>
为什么这样传参:
- 符合url的规范:
- 所以可以用query对象传参
导航守卫
全局守卫
所有的路由都会被过滤,也可以在特定的组件内创建局部守卫
主要监听页面的跳转
from从哪个组件来的
to去跳转到哪个组件
next()
- next(false)中断路由
- next(path)跳转到哪个页面,可用来做一些条件判断的跳转,比如login
- 其他的用的时候查官网
import Vue from 'vue' //替换成懒加载 // import Home from '../components/Home.vue' // import About from '../components/About.vue' // import User from '../components/User.vue' //懒加载: const Home = () => import('../components/Home.vue') const About = () => import('../components/About.vue') const User = () => import('../components/User.vue') const HomeChild = () => import('../components/HomeChild.vue') const Profile = () => import('../components/Profile') //1. 导入插件 import Router from 'vue-router' //2. 使用插件 Vue.use(Router) //3. 创建路由配置 const routes = [ { path: '/', redirect: '/home' }, { path: '/home', name: 'Home', component: Home, meta: { title: '首页' }, children: [ { path: '', // redirect:'child' }, { //这里不能同/开头,会自动加上 path: 'child', name: 'HomeChild', component: HomeChild, }] }, { path: '/about', name: 'About', component: About, meta: { title: '详情' }, }, { path: '/user/:userName', component: User, meta: { title: '用户' }, }, { path: '/profile', component: Profile, meta: { title: '档案' }, } ]; const router = new Router({ routes, mode: 'history', linkActiveClass: 'active' }) router.beforeEach((to, from, next) => { next() //匹配path中的meta对象的title document.title = to.matched[0].meta.title console.log(to); // console.log(from); // console.log("next: "+next); }) //4. 传入路由配置,导出路由对象 export default router
独享守卫
import Vue from 'vue'
//懒加载:
const Home = () => import('../components/Home.vue')
//1. 导入插件
import Router from 'vue-router'
//2. 使用插件
Vue.use(Router)
//3. 创建路由配置
const routes = [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'Home',
component: Home,
meta: {
title: '首页'
},
children: [
{
path: '',
// redirect:'child'
},
{
//这里不能同/开头,会自动加上
path: 'child',
name: 'HomeChild',
component: HomeChild,
beforeEnter: (to, from, next) => {
console.log("独享守卫");
next()
}
}]
}
];
const router = new Router({
routes,
mode: 'history',
linkActiveClass: 'active'
})
//4. 传入路由配置,导出路由对象
export default router
前置钩子和后置钩子
import Vue from 'vue'
//懒加载:
const Home = () => import('../components/Home.vue')
//1. 导入插件
import Router from 'vue-router'
//2. 使用插件
Vue.use(Router)
//3. 创建路由配置
const routes = [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'Home',
component: Home,
meta: {
title: '首页'
}
},
];
const router = new Router({
routes,
mode: 'history',
linkActiveClass: 'active'
})
//前置钩子 hook,像filter一样
router.beforeEach((to, from, next) => {
next()
//匹配path中的meta对象的title
document.title = to.matched[0].meta.title
console.log(to);
})
//后置钩子
router.afterEach((to,from)=>{
console.log("在跳转之后调用");
})
//4. 传入路由配置,导出路由对象
export default router
十、打包js的结构图
- app*.js所有的业务代码
- mainifest*.js代码转换的依赖的底层支持
- vendor*.js第三方插件
- .map文件:项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错。有了map就可以像未加密的代码一样,准确的输出是哪一行哪一列有错,可以设置:config/index.js productionSourceMap:false
十一、keep-alive组件
是vue的一个组件:保证一些组件进入缓存不用你每次请求解析资源,提高效率,在显示的地方配置
<template> <div id="app"> <router-link to="/home" tag="button" replace>首页</router-link> <router-link to="/about" replace>详情</router-link> <router-link :to="'/user/'+userName" replace>用户</router-link> <router-link :to="{path:'/profile',query:{name:'lisa',age:18},fragment:'4d5as46s'}" replace>档案</router-link> <button @click="toProfile">档案2</button> <!-- <button @click="homeClick">首页</button> <button @click="aboutClick">详细</button>--> <!-- <router-view></router-view> --> <!-- 保存到缓存中 --> <keep-alive> <router-view></router-view> </keep-alive> </div> </template>
keep-alive的组件才可以使用activated()、deactivated()
<template> <div> <h2>首页11</h2> <router-link :to="{path:'/home/child',query:{content:'child1'}}">child</router-link> <router-link :to="toChild2">child2</router-link> <router-view></router-view> </div> </template> <script> export default { name: "Home", data() { return { toChild2: { path: "/home/child2", query: { content: "child2" } }, path: "/home/child", query:{ childContent:'child1' } }; }, methods: {}, created() { console.log("Home组件被创建成功"); }, mounted() { console.log("组件被挂载成功"); }, updated() { console.log("组件中发生改变时"); }, destroyed() { console.log("home destroyed"); }, activated() { console.log("home 激活"); this.$router.push(this.path) }, deactivated() { console.log("home 离开"); }, beforeRouteLeave(to, from, next) { console.log('before leave home'); this.path = this.$route.path; console.log(this.path); next(); } }; </script> <style> </style>
keep-alive 的exclude、include属性
- exclude=“componentName,componentName...”,被排除在缓存之外,不能加空格
<keep-alive exclude="Profile"> <router-view></router-view> </keep-alive>
export default { name: "Profile", created() { console.log("profile created"); }, destroyed() { console.log("profile destroyed"); } };
十二、自定义tab-bar
/style中引用要用@import /
准备好tabbar.vue,调好样式,预留出来一个插槽,用来放具体的tabbar的item
<template> <div id="tab-bar"> <slot></slot> </div> </template> <script> export default { name: "TabBar", } </script> <style scoped> #tab-bar { display: flex; background-color: #fdfdff; /*显示在最下面和屏幕等宽*/ position: fixed; left: 0; right: 0; bottom: 0; /*阴影 fgba(最后是0.1表示透明度)*/ box-shadow: 0 -1px 1px rgba(100, 100, 100, .1); } </style>
封装tab-bar-item
<template> <div class="tab-bar-item" @click="itemClick"> <div v-if="!isActive"> <slot name="item-icon"></slot> </div> <div v-else> <slot name="item-icon-active"></slot> </div> <div :class="{active:isActive}"> <slot name="item-text"></slot> </div> </div> </template> <script> export default { name: "TabBarItem", props:{ path:{ type:String } }, data() { return { // isActive: true } }, computed:{ isActive(){ return this.$route.path.indexOf(this.path) !== -1 } }, methods:{ itemClick(e){ this.$router.replace(this.path) } } } </script> <style scoped> .tab-bar-item { flex: 1; text-align: center; /*一般移动端的tabbar都是49px*/ height: 49px; font-size: 14px; } .tab-bar-item img { width: 24px; height: 24px; margin-top: 3px; margin-bottom: 2px; /*可以去掉图片下面的三个像素*/ vertical-align: bottom; } .active { color: red; } </style>
注册到app.vue中
<template> <div id="app"> <router-view></router-view> <tab-bar> <tab-bar-item path="/home"> <img slot="item-icon" src="./assets/images/tabbar/home.png" alt="首页"> <img slot="item-icon-active" src="./assets/images/tabbar/home_active.png" alt=""> <div slot="item-text">首页</div> </tab-bar-item> <tab-bar-item path="/category"> <img slot="item-icon" src="./assets/images/tabbar/category.png" alt=""> <img slot="item-icon-active" src="./assets/images/tabbar/category_active.png" alt=""> <div slot="item-text">分类</div> </tab-bar-item> <tab-bar-item path="/cart"> <img slot="item-icon" src="./assets/images/tabbar/cart.png" alt=""> <img slot="item-icon-active" src="./assets/images/tabbar/cart_active.png" alt=""> <div slot="item-text">购物车</div> </tab-bar-item> <tab-bar-item path="/profile"> <img slot="item-icon" src="./assets/images/tabbar/profile.png" alt=""> <img slot="item-icon-active" src="./assets/images/tabbar/profile_active.png" alt=""> <div slot="item-text">我的</div> </tab-bar-item> </tab-bar> </div> </template> <script> import TabBar from "./components/tabbar/TabBar"; import TabBarItem from "./components/tabbar/TabBarItem"; export default { name: 'App', components: { TabBar, TabBarItem } } </script> <style> /*style中引用要用@*/ @import "./assets/css/base.css"; </style>
可以优化class,颜色直接写死不合适
还可以从父组件传过来,然后绑定style来设置
十三、路径配置别名
vue-cli2
配置别名:\build\webpack.base.conf.js
resolve: { extensions: ['.js', '.vue', '.json'], alias: { '@': resolve('src'), 'assets': resolve('src/assets'), 'components': resolve('src/components'), 'views': resolve('src/views'), } },
项目中使用
html中: 前面要加 ~
<tab-bar-item path="/home" activeColor="blue"> <img slot="item-icon" src="~assets/images/tabbar/home.png" alt="首页" /> <img slot="item-icon-active" src="~assets/images/tabbar/home_active.png" alt /> <div slot="item-text">首页</div> </tab-bar-item>
import中使用
import TabBarItem from "components/tabbar/TabBarItem";
vue-cli3
- 根目录下新建vue.config.js
- 在vue.config.js中的chainWebpack中配置config.resolve.alias.set('@', resolve('src')).set('components', resolve('src/components'));
十四、promise
是什么?
是异步编程的一种解决方案
什么时候使用异步呢?
- 网络请求
- 回调函数的时候
promise用法
- 构造器有一个参数,是函数,这个函数有两个参数都是函数
- resolve:异步请求成功调的函数,被调用之后会调用then()
- then:来处理业务代码,参数是一个函数,可通过resolve来传入data
- reject:异步失败的时候调的函数,也可以传输数据到catch
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('传入then 中的 data')
}, 1500)
}).then(data => {
console.log(data);
return new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('内部的resolve')
reject('内部的reject')
}, 1500)
})
}).catch(data => {
console.log(data);
})
promise异步完成后会有三种状态
- pendding等待
- fullfill 完全满足
- reject 拒绝|次品
promise的另一种写法
- then中也可以传两个函数,第一个是成功,第二个是失败
new Promise((resolve, reject) => { setTimeout(() => { resolve('传入then 中的 data') // reject('失败') }, 1500) }).then(data => { console.log(data); },reject => { console.log(reject); })
- 再简化
- new Promise(resolve) ==>Promise.resolve(data) ==> data
- throw 'msg'也会被catch()捕获
// new Promise(resolve) ==>Promise.resolve(data) ==> data
//throw 'msg'也会被catch()捕获
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('第一层...')
}, 1500)
}).then(data => {
console.log(data);
return Promise.resolve('第二层...')
// return Promise.reject('额鹅鹅鹅')
throw 'dsadsa'
}).then(data=>{
console.log(data);
return 'aaa'
}).then(data=>{
console.log(data);
}).catch(err=>{
console.log(err);
})
- Promise.all([PromiseInstance...])
- 多个异步请求同时等待成功后才执行后续代码
- 像是java-juc的栅栏
Promise.all([
new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve('1111111')
},1000)
}),
new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve('222222')
},2000)
})
]).then(data=>{
//1111111,222222
console.log(data.toString())
})
十五、vuex
介绍
- 是什么?
- 是为vue程序提供一个集中状态管理模式和库
- 充当应用程序中所有组件的特殊共享变量的集中存储
- 这些共享状态都是响应式的
- vuex修改状态的流程
通过提交 mutation 的方式,而非直接改变
store.state.count
,是因为我们想要更明确地追踪到状态的变化。这个简单的约定能够让你的意图更加明显,这样你在阅读代码的时候能更容易地解读应用内部的状态改变。此外,这样也让我们有机会去实现一些能记录每次状态改变,保存状态快照的调试工具。有了它,我们甚至可以实现如时间穿梭般的调试体验。由于 store 中的状态是响应式的,在组件中调用 store 中的状态简单到仅需要在计算属性中返回即可。触发变化也仅仅是在组件的 methods 中提交 mutation。
actions步骤可以省略,一般异步的操作放在actions中完成后放在mutations中
mutations只能是同步的操作,devtools监听不到异步操作
使用步骤
store用法
state用法
state中所有的已定义的属性都是响应式的,新加入的不被响应:因为属性初始化后,都被一个dep对象=【watcher,watcher..】监控,后面加入的不受监控
npm install vuex --save
- 新建、src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
//1.安装,底层会调用Vuex.install
Vue.use(Vuex)
// 2.创建对象
const store = new Vuex.Store({
state: {
count: 0
}, mutations: {
//state必须传,默认会传进来
increment(state) {
state.count++
}
}, actions: {}, getters: {}, modules: {}
})
// 3.导出store对象
export default store
- main.js挂载插件
import Vue from 'vue'
import App from './App'
import store from "./store";
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
store,
render: h => h(App)
})
- App.vue
<template>
<div id="app">
<h2>{{$store.state.count}}</h2>
<button @click="increment">+</button>
<hello-vuex></hello-vuex>
</div>
</template>
<script>
import HelloVuex from "./components/HelloVuex";
export default {
name: 'App',
methods:{
increment(){
this.$store.commit('increment')
}
},
components: {
HelloVuex
}
}
</script>
<style>
</style>
- HelloVuex.vue
<template>
<div>
<h2>{{$store.state.count}}</h2>
</div>
</template>
<script>
export default {
name: "HelloVuex"
}
</script>
<style scoped>
</style>
getters用法
有点像computed的概念
- App.vue
<template>
<div id="app">
<h2>{{$store.state.count}}</h2>
<button @click="increment">+</button>
<h2>年龄大于20:{{$store.getters.more20Person}}</h2>
<h2>年龄大于20个数:{{$store.getters.more20PersonCount}}</h2>
<h2>年龄大于age个数:{{$store.getters.moreAgePerson(13)}}</h2>
<hello-vuex></hello-vuex>
</div>
</template>
<script>
import HelloVuex from "./components/HelloVuex";
export default {
name: 'App',
methods:{
increment(){
this.$store.commit('increment')
}
},
components: {
HelloVuex
}
}
</script>
<style>
</style>
- store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
//1.安装,底层会调用Vuex.install
Vue.use(Vuex)
// 2.创建对象
const store = new Vuex.Store({
state: {
count: 0,
persons: [
{name: 'a', age: 12},
{name: 'b', age: 23},
{name: 'c', age: 32},
{name: 'd', age: 24}
]
}, mutations: {
//state必须传,默认会传进来
increment(state) {
state.count++
}
},
actions: {},
//最多只能写两个参数时state,getters,默认会传进来
getters: {
more20Person(state) {
return state.persons.filter(per=>per.age>20)
},
more20PersonCount(state,getters){
// 这里不用写括号
return getters.more20Person.length
},
//返回一个函数可以传动态的参数
moreAgePerson(state){
return (age)=>{
return state.persons.filter(per=>per.age>age)
}
}
},
modules: {}
})
// 3.导出store对象
export default store
mutations
- 官方规定修改state只能用mutations
- 分为两部分函数名叫做字符串时间类型
- 代码块叫做回调函数
- 可以传多个参数第一个是store,后面的自定义叫做payload(负载 )
//store/index.js
mutations: {
//state必须传,默认会传进来
increment(state) {
state.count++
},
add(state,num){
state.count +=num
}
}
//app.vue
methods:{
increment(){
this.$store.commit('increment')
},
add(num){
this.$store.commit('add',num)
}
}
第二种提交风格
- 提交的参数会当成一个对象来取
inc(num){
this.$store.commit({
type:'inc',
num
})
}
mutations: {
//state必须传,默认会传进来
increment(state) {
state.count++
},
add(state,num){
state.count +=num
},
//当成对象处理参数
inc(state,payLoad){
state.count +=payLoad.num
}
}
响应式操作
update(state){
//响应式
// state.persons.push({name:'e',age:99})
//响应式
// state.person={name:'f',age:101}
//新加的属性不会被监控,只有在其他任意的属性变化一次后他会刷新一次
// state.person.add=111
// state.person['address']=222
//删除一个对象的属性
// delete state.person.age
//vue set value是响应式的,key必须是字符串
// Vue.set(state.person,'asd','vue set value是响应式的')
Vue.delete(state.person,'age')
}
mutations中方法的官方定义
避免写错,定义一个常量对象,在使用的文件中导入
定义
[const](){}
//mutation-type.js
export const INCREMENT='increment'
export const ADD='add'
export const INC='inc'
export const UPDATE='update'
import {INCREMENT,ADD,UPDATE,INC} from "./mutation-type";
//app.vue
update(){
this.$store.commit({
type:UPDATE,
})
}
//index.js
mutations: {
//state必须传,默认会传进来
[INCREMENT](state) {
state.count++
},
[ADD](state,num){
state.count +=num
},
//当成对象处理参数
[INC](state,payLoad){
state.count +=payLoad.num
},
[UPDATE](state){
Vue.delete(state.person,'age')
}
}
actions
- mutations的异步方法修改的数据,插件是跟踪不到的
- 所有的异步操作都应该放在actions中处理,处理后的回调放在mutations中处理
- 修改state的唯一途径就是mutations
- actions中的默认参数是上下文context(context=store)
action处理异步操作:
//app.vue
aUpdate(){
// this.$store.dispatch('aUpdate',{
// msg:'参数信息',
// success:(data)=>{console.log(data)}
// })
//第二种方法,异步函数返回的promise对象
this.$store.dispatch('aUpdate',{
msg:'参数信息'
}).then(res=>{
console.log('完成异步操作');
console.log(res);
})
}
//index.js
actions: {
// aUpdate(context,payload) {
// // console.log('默认参数是上下文对象: ',context)
// setTimeout(function () {
// context.commit('aUpdate',payload)
// }, 1000)
// }
//第二种方式返回一个promise对象,在调用处可以使用
aUpdate(context, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
context.commit('aUpdate', payload)
resolve(12312)
}, 1000)
})
}
}
modules
- 只在一个state中存数据可能因为数据量过大而臃肿,所以在modules中分多个模块
- 取值$store.state. modulesName .propertise
- 子模块:
- state:需要指定模块名,可以和父模块同名
- getters : 和父模块同名会报错,可以直接访问不需要指定模块名
- actions|mutations : 和父模块名字相同都会调用,先调用父模块的,所以不要定义相同的名字
显示:app.vue
<h2>-------------state--modules的内容---------</h2>
<h2>{{$store.state.a.name}}</h2>
<h2>{{$store.getters.getModuleA}}</h2>
<h2>{{$store.getters.getModuleA_add('age')}}</h2>
<h2>{{$store.getters.getModuleA_add_root}}</h2>
<button @click="moduleA">模块a修改name</button>
<button @click="asyncUpdateModuleA">异步模块a修改name</button>
methods:{
moduleA() {
this.$store.commit('aUpdate','模块a名字修改')
},
asyncUpdateModuleA(){
this.$store.dispatch('asyncUpdateModuleA')
}
}
index.js
modules: {
a:{
//需要指定模块名,可以和父模块同名
state:{name:'module_a',person:123},
//和父模块同名会报错,可以直接访问不需要指定模块名
getters:{
getModuleA(state){
return state.name+'_getModuleA'
},
getModuleA_add(state,getters){
return (age) => {
return getters.getModuleA+age
}
},
//三个默认参数
getModuleA_add_root(state,getters,rootState){
return state.name+getters.getModuleA+'_add_'+rootState.count
}
},
// 和mutations使用差不多
actions:{
//也可以使用对象的解构,详见es6
asyncUpdateModuleA(context){
setTimeout(()=>{
context.commit('aUpdate','异步修改子模块')
},1000)
}
},
mutations:{
//和父模块名字相同都会调用,先调用父模块的,所以不要定义相同的名字
aUpdate(state,payload){
state.name=payload
console.log('child mutations 被调用')
}
},
modules:{}
},
//模块b
b:ModuleB
}
抽离index.js
- state一般是不抽取出来的
- modules是新建一个./modules/文件夹,在里面建立模块
- 抽离好的文件
import Vue from 'vue'
import Vuex from 'vuex'
import mutations from "./mutations";
import actions from "./actions";
import getters from "./getters";
import module_a from "./modules/module_a";
//1.安装,底层会调用Vuex.install
Vue.use(Vuex)
// 2.创建对象
const store = new Vuex.Store({
state: {
count: 0,
persons: [
{name: 'a', age: 12},
{name: 'b', age: 23},
{name: 'c', age: 32},
{name: 'd', age: 24}
],
person: {
name: 'g',
age: 100
}
},
mutations,
actions,
getters,
modules: {
a: module_a
}
})
// 3.导出store对象
export default store
十六、vxios
介绍
- 为什么选则它
- vue作者不再维护vue-resource,推荐使用vxios
- 可以在node环境中使用
- 可以拦截请求
- 可以拦截响应数据处理
- 支持的请求方式,满足resful,和jq的有点像
- axios(config) :默认是get,参数是一个对象
- axios.request(config)
- axios.get(url [, config])
- axios.delete(url [, config])
- axios.head(url [, config])
- axios.post(url [, data [, config] ])
- axios.put(url [, data [, config] ])
- axios.patch(url [, data [, config] ])
- 内部封装了promise
使用
初步使用
import axios from 'axios'
axios.defaults.baseURL = 'https://httpbin.org'
axios.defaults.timeout = 5000
axios({
// url:'http://123.207.32.32:8080/home/mutidata',
url: 'post',
method: 'post',
// 拼接在URL后
params: {
name: 1
},
// 请求体中的参数
data: {
type: 'sell',
page: 3
},
//拦截请求
transformRequest:[function (query) {
}],
//拦截返回数据
transformResponse:[function (response) {
}],
}).then(res => {
console.log(res);
})
// 同时处理多个异步请求,最后返回一个数据数组,像java的栅栏
axios.all([axios({url: 'post', method: 'post'}), axios({url: 'get'})]).then(res => {
console.log(res);
})
//处理返回的结果数组,使用的是数组的解构是根据下标解构的
axios.all([axios({url: 'post', method: 'post'}), axios({url: 'get'})])
.then(([res1, res2]) => {
console.log(res1);
console.log(res2);
})
// 这样也可以
axios.all([axios({url: 'post', method: 'post'}), axios({url: 'get'})])
.then(
axios.spread((res1, res2) => {
console.log(res1);
console.log(res2);
}))
进一步封装
避免使用全局的axios,可能每个模块的请求是不一样的
- 使用的时候导入就可以了
- transformrequest和axiosInstance.interceptors.request.use 不冲突后者先调用
- transformResponse和axiosInstance.interceptors.response前者先调用
新建/network/request.js
import axios from "axios";
export function request(config) {
if (!config.baseURL) {
config.baseURL = 'https://httpbin.org'
}
if (!config.timeout) {
config.timeout = 5000;
}
const axiosInstance = axios.create(config);
//req是请求参数对象
axiosInstance.interceptors.request.use(req => {
console.log(req);
//1.可以修改一些请求的参数
// 2.可以设置一个加载图片
return req
})
//res是返回的对象
axiosInstance.interceptors.response.use(res => {
console.log(res.data);
return res.data
})
return axiosInstance(config);
}
十七、细节
- this.$refs.[refName]只会取当前模块的引用
- style标签里的scoped只会作用当前的组件的css
- 组件是不能直接监听原生事件的,需要:@click.native=""
加载全部内容