特点
1.基于MVVM模式(Model-View-ViewModel),中间通过viewmodel层实现了双向绑定,其作为中间层会去监控后端数据的变化,并根据数据的变化去更新对应的前端展示内容,因此使用该框架就只需要操作数据而不用再通过操作DOM来手动更新视图。(JQuery框架由于需要频繁操作DOM,所以会降低性能)
2.模块化开发和虚拟DOM(减少真实DOM操作,在内存中模拟DOM操作,提升前端渲染效率)
理解Vue当中的双向绑定
通过下面的例子来了解为何说Vue实现了双向绑定,因此只需操作数据而无需关心其他:
<body>
<div id="app">
<ul v-for="n in name.length"><input type="text" v-model="name[n-1]">你是:{{name[n-1]}}
</ul>
</div>
</body>
<script src="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
name: [111, 222, 333]
}
});
</script>
在上面的代码中使用for循环依次将data里的name打印出来,并且当修改表单中的值时,对应显示的数据以及name里的数据也会同时更改,可以看出页面会动态监听name数据的变化,同时当表单值发生变化时也会动态修改name里的值。
再比如依次执行下面的语句:
app.name.push(444)
app.name.push(555)
app.name.pop()
可以看出是对name数组进行数据插入和弹出操作,期间也可以看见页面显示的数据在不断地修改
MVVM
Model:模型层,表示JavaScript对象
View:视图层,表示DOM
ViewModel:连接视图和模型的中间件,Vue主要就是这层的实现者,其核心就是实现了DOM监听和数据绑定(Ajax请求与后端进行数据交互,并根据数据变化更新前端视图展示;同时也监听前端视图的变化,并通知后端数据的改变)。
配置环境
非常好用的一个前端框架,配置方法和jquery之类的相似,都是引用vue的源文件就行,地址:
https://www.bootcdn.cn/vue/
一般选择标准版:https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.js
将js代码导入即可使用,比如下面代码:
<script src="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.js"></script>
使用步骤
(1)导入vue.js
(2)导入自己写的js文件
(3)实例化一个Vue对象,设置id键为el
(element),数据都以对象形式保存在data键里,函数则保存在methods键里
(4)一般调用时通过id绑定,data里的数据通过{{ 属性 }}
来读取
举例:
<body>
<div id="app">
{{name}}
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
name: 'aaa'
}
});
</script>
可以设置属性data并将对象存放到该值里,也可以将data设置为一个函数,举例:
<body>
<div id="app">
{{name}}
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data() {
// 设置为方法,效果和前面相同
return {
name: 'aaa'
}
}
});
</script>
Vue对象常用属性
官方api:https://cn.vuejs.org/v2/api/
el
element,代表元素,实例化vue类的id
data
存放数据
methods
存放函数,举例:
<body>
<div id="app">
<button v-on:click="abc(1)">click</button> {{cde()}}
<!-- 直接调用方法要记得后面加括号 -->
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script>
var app = new Vue({
el: '#app',
data: {},
methods: {
abc: function(x) {
alert(x)
},
cde: function() {
return "xxx";
}
}
});
</script>
computed
监测并计算值,虽然methods也可以进行计算,但是computed会进行缓存,效率更高,并且监测的值是在computed当中定义的而非data里的属性值,当值没发生变化时则保存缓存的结果,否则重新计算,举例:
<body>
<div id="app">
<div>{{sum}}</div>
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script>
var app = new Vue({
el: '#app',
data: {
age: [10, 20]
},
computed: {
sum: function() {
// 计算sum的值,computed的属性不能在data里定义过
return this.age[0] + this.age[1];
}
}
});
</script>
watch
也是监听监听值,但监听的是data里属性值的变化,功能上和computed有点像,举例:
<body>
<div id="app">
<button @click="add">add</button> {{ num }}:{{ count }}
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script>
var app = new Vue({
el: '#app',
data: {
count: 1,
num: 0
},
methods: {
add: function() {
this.count ++;
}
},
watch: {
count: function (new_val, old_val) {
// 监听data里的count属性,当值发生变化时,会将新的值和原来的值传入,并执行该方法
console.log(new_val, old_val);
this.num++;
}
}
});
</script>
其还可以通过deep
属性监听对象的变化,举例:
watch: {
o: {
handler: function(new_val, old_val) {
...
},
deep: true
}
}
更多watch内容以及和computed区别参考:https://blog.csdn.net/weixin_43837268/article/details/92769669
mixins
可以引入对象里的属性,从而实现对象属性复用,比如定义一个对象存放很多data和methods,然后就可以在很多Vue对象里通过mixins
属性引入复用属性(一般出现同名冲突时,mixins
的一般会被覆盖;如果同名冲突的是钩子函数,那么就是先执行mixins中的钩子函数,然后再执行本身的钩子函数),举例:
<body>
<div id="app">
<span @click="test">{{name}}</span>
<!-- 引入复用的base对象,显示aaa -->
</div>
<div id="app1">
<span @click="test">{{name}}</span>
<!-- 复用的数据被覆盖,因此显示bbb -->
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var base = {
// 定义一个对象里面存放了Vue属性,包括可复用的数据和方法
data: {
name: 'aaa',
},
methods: {
test: function() {
this.name = 'ccc';
}
}
}
var app = new Vue({
el: '#app',
mixins: [base],
// 通过mixins属性复用存放Vue属性的对象
});
var app1 = new Vue({
el: '#app1',
mixins: [base],
data: {
name: 'bbb'
// 若属性出现重复,则会将mixins中的覆盖
}
});
</script>
全局引入:可以通过Vue.mixin({...})
进行全局引入,此时所有基于该Vue对象创建的实例都将被加上该内容,举例:
Vue.mixin({
mounted: function() {
console.log(this.name);
// 所有实例挂载完成后都会属性其name
},
data: () => ({
a: 100
})
});
生命周期
beforeCreate
在创建对象之前执行,可以做一些比如加载动画等
created
在创建对象之后、dom加载完成前执行,可以异步获取一些数据
beforeMount
在虚拟dom中执行编译模板中,dom还未生成
mounted
编译模板完成,此时页面内容将显示出来,此时dom生成完成
beforeUpdate
在组件更新事件前执行
updated
在组件更新事件后执行
beforeDestory
在销毁事件前执行
destoryed
在销毁事件后执行
多组件时生命周期顺序
同步父子组件
- 加载顺序:
父beforeCreate -> 父created -> 父beforeMount -> 子1beforeCreate -> 子1created -> 子1beforeMount -> 子2beforeCreate -> 子2created -> 子2beforeMount -> ... -> 子1mounted -> 子2mounted -> ... -> 父mounted - 更新顺序:
父beforeUpdate -> 子1beforeUpdate -> 子2beforeUpdate -> 子2updated -> 子1updated -> ... -> 父updated - 销毁顺序:
父beforeDestroy -> 子1beforeDestroy -> 子1destroyed -> 子2beforeDestroy -> 子2destroyed -> ... -> 父destroyed
同步同级组件
- 多个同级组件加载顺序:
同级1beforeCreate -> 同级1created -> 同级1beforeMount -> 同级2beforeCreate -> 同级2created -> 同级2beforeMount -> ... -> 同级1mounted -> 同级2mounted -> ... - 多个同级组件更新顺序:
同级1beforeUpdate -> 同级2beforeUpdate -> 同级2updated -> 同级1updated -> ... - 多个同级组件销毁顺序:
同级1beforeDestroy -> 同级1destroyed -> 同级2beforeDestroy -> 同级2destroyed -> ...
异步父子组件
- 加载顺序:
父beforeCreate -> 父created -> 父beforeMount -> 父mounted -> 父beforeUpdate -> 子1beforeCreate -> 子1created -> 子1beforeMount -> 子1mounted -> 父updated -> 父beforeUpdate -> 子2beforeCreate -> 子2created -> 子2beforeMount -> 子2mounted -> 父updated -> ... - 更新顺序:和同步一样
- 销毁顺序:和同步一样
异步同级组件
- 多个同级组件加载顺序:
同级1beforeCreate -> 同级1created -> 同级1beforeMount -> 同级1mounted -> 同级2beforeCreate -> 同级2created -> 同级2beforeMount -> 同级2mounted -> ... - 多个同级组件更新顺序:和同步一样
- 多个同级组件销毁顺序:和同步一样
代码示例
- 父组件
<template>
<div>
<button @click="change">{{ label }}</button>
<child1 ref="child1"></child1>
<child2 ref="child2"></child2>
</div>
</template>
<script>
// 同步
// import child1 from "./child1";
// import child2 from "./child2";
// 异步
const child1 = () => import("./child1");
const child2 = () => import("./child2");
export default {
name: "parent",
components: {
child1,
child2
},
data() {
return { label: "parent" };
},
methods: {
change() {
this.$refs.child1.label += ".";
this.$refs.child2.label += ".";
this.label += ".";
}
},
beforeCreate() {
console.log("parent beforeCreate");
},
created() {
console.log("parent created");
},
beforeMount() {
console.log("parent beforeMount");
},
mounted() {
console.log("parent mounted");
},
beforeUpdate() {
console.log("parent beforeUpdate");
},
updated() {
console.log("parent updated");
},
beforeDestroy() {
console.log("parent beforeDestroy");
},
destroyed() {
console.log("parent destroyed");
}
};
</script>
- 子组件1:
<template>
<div>
{{ label }}
</div>
</template>
<script>
export default {
name: "child1",
data() {
return { label: "child1" };
},
beforeCreate() {
console.log("child1 Create");
},
created() {
console.log("child1 created");
},
beforeMount() {
console.log("child1 beforeMount");
},
mounted() {
console.log("child1 mounted");
},
beforeUpdate() {
console.log("child1 beforeUpdate");
},
updated() {
console.log("child1 updated");
},
beforeDestroy() {
console.log("child1 beforeDestroy");
},
destroyed() {
console.log("child1 destroyed");
}
};
</script>
- 子组件2:
<template>
<div>
{{ label }}
</div>
</template>
<script>
export default {
name: "child2",
data() {
return { label: "child2" };
},
beforeCreate() {
console.log("child2 Create");
},
created() {
console.log("child2 created");
},
beforeMount() {
console.log("child2 beforeMount");
},
mounted() {
console.log("child2 mounted");
},
beforeUpdate() {
console.log("child2 beforeUpdate");
},
updated() {
console.log("child2 updated");
},
beforeDestroy() {
console.log("child2 beforeDestroy");
},
destroyed() {
console.log("child2 destroyed");
}
};
</script>
参考:https://blog.csdn.net/weixin_39147099/article/details/82956439
this下对象
$refs
绑定DOM对象,代替了原生的documentByxxx
的方法,举例:
<body>
<div id="app">
<button @click="test" ref="msg1">{{msg}}</button>
<!-- 设置dom标志为msg1 -->
</div>
</body>
<script src="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
msg: "aaa"
},
methods: {
test() {
this.$refs.msg1.style.background = 'black';
// 获取dom元素,并修改背景色
console.log(this.$refs.msg1);
// <button style="background: black;">aaa</button>
}
}
});
</script>
指令
一般写在标签里,和属性的使用比较像,Vue里很多指令有自带功能,比如v-model
能够获取当前表单的值,v-show
表示值为空时不显示标签,举例:
<body>
<div id="app">
<input type="text" v-model="name">
<span v-show="name">你是:{{name}}</span>
</div>
<!-- 此时表单的值和span标签内容双向绑定,而且一旦表单内容为空,span标签也为空(连"你是:"都不显示) -->
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
name: 'aaa'
}
});
</script>
v-model
绑定表单和变量的值,前面例子就是一直基本用法,其下还有一些常用属性,比如:lazy
——一般情况下表单的值和变量值会实时同步更新,但是设置lazy后,只有当鼠标点击表单外部时,变量的值才会更新;trim
——把头和尾的空格都删掉再存到变量里;number
——把值变成num型存到变量
注:
v-model只在<input>
/<textarea>
/<select>
三个标签里面使用
注:
设置数据值的时候要根据数据类型来进行设置,比如多选框存的是一堆数据,所以需要用数组,举例:
<body>
<div id="app">
男:<input type="radio" v-model="sex" value="男"> 女:
<input type="radio" v-model="sex" value="女"> 吃:
<input type="checkbox" v-model="hobby" value="eat"> 喝:
<input type="checkbox" v-model="hobby" value="drink">
<p>{{sex}}{{hobby}}</p>
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
"sex": "女",
"hobby": []
// 字符串存放单选框数据,数组存放多选框数据
}
});
</script>
注1:
vue监听数组问题:对于监听数组时,如果数组当中的某一个值被修改,或者长度被修改,比如对于下面的代码:
<body>
<div id="app">
<ul v-for="n in name.length"><input type="text" v-model="name[n-1]">你是:{{name[n-1]}}
</ul>
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
name: [111, 222, 333]
}
});
</script>
不论输入下面任何一个命令,vue都不会监听到值的修改:
app.name[0] = "xxx"
// 修改数组的第一个值
app.name.length =2
// 修改数组长度,只取前两个
原因就是因为受到js本身的限制,vue只能监听数组的变化而无法监听到数组内部的变化,因此只有部分的方法操作能够被vue监听到,而支持监听的方法官方也进行了说明:链接(支持:push()
/pop()
/shift()
/unshift()
/splice()
/sort()
/reverse()
),为了解决上述问题,这里可以使用splice()
方法来修改数组内容或者改变数组长度,举例:
app.name.splice(0, 1, "aaa")
// 将数组第一个值改为aaa
app.name.splice(2, 1)
// 删除数组的第三个值
因为splice(0,0)
将不会修改数组的内容,所以当数组内容发生改变,而我们又仅仅想更新数据的显示,那么可以通过执行一次splice(0,0)
来实现,或者使用官方的强制更新——$forceUpdate()
方法来实现
更多splice
使用参考:
注2:
vue监听对象问题:首先,vue不允许动态添加根级别的属性(本身在没定义根级别的属性时调用该属性就会报错),并且也无法监听对象下属性的添加和删除,但是可以通过内置的$set
/$delete
方法实现,举例:
<body>
<div id="app">
<button @click="handle">add age attr</button>
people info:{{ people }}
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script>
var app = new Vue({
el: "#app",
data: {
people: {
name: "aaa"
}
},
methods: {
handle() {
// this.people.age = 100
// vue无法检测对象的修改和删除
this.$set(this.people, "age", 100);
// 可以通过$set方法实现响应监听
}
}
});
</script>
v-for
for循环,举例:
<body>
<div id="app">
<ul v-for="n in nameList">你是:{{n.name}}<br>年龄:{{n.age}}</ul>
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
nameList: [{
name: 'aaa',
age: 20
}, {
name: 'bbb',
age: 30
}, {
name: 'aaa',
age: 25
}]
}
});
</script>
注:
v-for
循环时默认还会传第二个参数进来,为索引下标,举例;
<body>
<div id="app">
<ul v-for="(n, index) in nameList">编号:{{index}} <br> 你是:{{n.name}} <br> 年龄:{{n.age}}</ul>
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
nameList: [{
name: 'aaa',
age: 20
}, {
name: 'bbb',
age: 30
}, {
name: 'aaa',
age: 25
}]
}
});
</script>
注2:
在组件化开发当中,使用v-for
指令应该设置一个key属性,并且该属性不会重复,因此一般选择第二个参数索引index
作为key,比如把上面v-for
那行修改如下:
<ul v-for="(n, index) in nameList" :key="index">编号:{{index}} <br> 你是:{{n.name}} <br> 年龄:{{n.age}}</ul>
<!-- 获取参数n和index时也应该用括号包起来-->
v-if/v-else-if/v-else
判断语句,当条件正确或者值不为空的时候显示内容,使用举例:
<body>
<div id="app">
<div v-for="n in name">
<span v-if="n == 'bbb'">b:{{n}}</span>
<span v-else-if="n == 'ccc'">c:{{n}}</span>
<span v-else>can't display</span>
</div>
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
url: "http://www.baidu.com",
name: ["aaa", "bbb", "ccc"]
}
});
</script>
v-show
当值为非空时或者条件为真展示内容,和v-if
很相似,但是还是有一点区别:当条件为false或者值为空时,v-if的所在的标签将会消失,而v-show仅仅是将内容通过display=none
进行隐藏,比如下面的例子,你可以将v-if
改成v-show
对比观察代码变化的不同:
<body>
<div id="app">
<input type="text" v-if="name" v-model="name">{{name}}
<!-- 尝试将这里的v-if改成v-show -->
</div>
</div>
</body>
<script src="./vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
name: "aaa"
}
});
</script>
v-bind
动态绑定标签属性值,举例:
<body>
<div id="app">
<a v-bind:href=url>click</a>
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
url: "http://www.baidu.com"
}
});
</script>
此时在命令行里就可以通过下面命令来修改链接:
app.url = ""
注:
一般情况下v-bind
可以直接用引号替代,比如上面的就可以改成:
<a :href=url>
注2:
vue里还提供了简易的对象传值方式,举例:
<body>
<div id="app">
<a :class="{active: judge, unactive: !judge}">click</a>
<!-- 判断如果judge值为true,则类为active,否则类为unactive -->
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
judge: true,
}
});
</script>
注3:
可以通过v-bind
传入一个对象,那么会以对象的key作为属性名,value作为对应的值进行批量绑定
<body>
<div id="app">
<a v-bind="options">click</a>
<!-- v-bind传入一个对象可以批量绑定,其中对象的key作为属性名,value作为对应的值 -->
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: "#app",
data: {
// options是一个对象
options: {
href: "http://www.baidu.com",
name: "aaa"
}
}
});
</script>
注4:
在2.6版本新增了属性名的绑定(原来只能绑定属性值),通过[xxx]
绑定,举例:
<body>
<div id="app">
事件名:<input type="text" v-model="event">
<button @[event]=handle>{{event}}</button>
<!-- @(v-on指令的简写,下一个指令就介绍这个)绑定事件名,执行对应事件 -->
</div>
</body>
<script type="text/javascript" src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
event: "click",
},
methods: {
handle(){
alert("你触发了:" + this.event + "事件!")
}
},
});
</script>
v-on
绑定事件,类似于原来的onclick
,对应的事件写在vue对象的methods
属性里,举例:
<body>
<div id="app">
<button v-on:click="abc(2)">click</button>
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
url: "http://www.baidu.com"
},
methods: {
abc: function(x) {
alert(x)
}
}
});
</script>
注:
一般情况下v-on:
可以替换成@
,所以上面的可以改成:
<button @click="abc(1)">
注2:
对于多事件绑定,可以通过传入对象实现,举例:
<body>
<div id="app">
<button v-on="{click:clicked, mouseenter:mouseentered}">click</button>
<!-- 注意v-on后面的冒号要换成= -->
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {},
methods: {
clicked: function(e) {
console.log(e);
},
mouseentered: function() {
alert("mouseenter");
}
}
});
</script>
其实原来仅根据函数绑定和通过传对象绑定事件是有区别的:
前者相当于:onclick="function xxx()"
,而后者相当于jquery的事件绑定语句:$(xxx).click(function(e){console.log(e);})
,因此打印以后可以发现e是点击事件
简单总结就是:前面是绑定函数,即只是调用一个函数,可以进行传参;而后者是绑定事件,无法进行传参
注3:
v-on
还绑定了一些事件方法,如阻止事件传播-stopPropagation()
,则用.stop
,取消默认事件-preventDefault()
,则用.prevent
,如监听键盘的按下某个键,则用keydown.键
,举例:
<body>
<div id="app">
<form @keydown.a='aaa' @submit.prevent='bbb'>
<!-- 按下a键时执行aaa方法,提交时阻止事件传播 -->
<input type="text">
<input type="submit">
</form>
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {},
methods: {
aaa: function(e) {
console.log(e);
},
bbb: function(e) {
// 使用了.prevent相当于在这里调用了:e.preventDefault();
console.log(e);
},
}
});
</script>
注4:
v-on
基本支持所有原生事件,事件参考:https://developer.mozilla.org/zh-CN/docs/Web/Events
注5:
默认事件会传递的参数可以通过$event
接收,举例:
<body>
<div id="app">
<button v-on:click="abc($event, 2)">click</button>
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
url: "http://www.baidu.com"
},
methods: {
abc: function(x, y) {
console.log(x, y);
}
}
});
</script>
v-html
将数据按html格式输出,举例:
<body>
<div id="app">
<span>{{raw}}</span>
<!-- 可以看到这里输出的是原始字符串 -->
<span v-html="raw"></span>
<!-- 会发现输出的不会是原始字符串,而是编译后的代码 -->
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
raw: `<h1>这是一行html代码<h1>`
}
});
</script>
自定义指令
前面那些指令都是vue里自带的,但我们也可以自己来定义vue,通过:Vue.directive()
来定义,举例:
<body>
<div id="app">
<span v-print="name" id="app">111</span>
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
Vue.directive('print', function(el, data) {
// 这里定义了一个v-print
// 默认自定义指令里接收第一个参数是组件本身所以这里el代表<span>
// data才是接收的参数,但接收后的data是个对象,真正接收到的值在其属性value里
el.style.background = '#000000';
console.log(data.value);
// 显示传入的name的值,即:aaa
})
new Vue({
el: '#app',
data: {
name: 'aaa'
}
});
</script>
自定义指令传入多属性
上面的自定义指令只传入一个值,如果想要再传入几个属性,可以在指令后通过.attr1.attr2...
实现,此时这些属性就会存入到接收数据对象的modifiers
属性里,值是true
,举例:
<body>
<div id="app">
<span v-print.a.b.c="name" id="app">111</span>
<!-- 传入a、b、c三个属性 -->
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
Vue.directive('print', function(el, data) {
el.style.background = '#000000';
console.log(data.modifiers);
// 可以看到结果为:{a: true, b: true, c: true}
})
new Vue({
el: '#app',
data: {
name: 'aaa'
}
});
</script>
自定义指令传入参数
如果想要给指令传入参数,可以通过下面方式实现:
<body>
<div id="app">
<span v-print:s.a.b.c="name" id="app">111</span>
<!-- 传入参数s,然后再传a、b、c三个属性 -->
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
Vue.directive('print', function(el, data) {
el.style.background = '#000000';
console.log(data.arg);
// 可以看到结果为:s
})
new Vue({
el: '#app',
data: {
name: 'aaa'
}
});
</script>
局部自定义指令
往Vue对象的directives
属性里添加即可,举例:
<body>
<div id="app">
<span v-print:s.a.b.c="name" id="app">111</span>
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#app',
data: {
name: 'aaa'
},
directives:{
// 局部自定义指令
print(el, data){
el.style.background = '#000000';
console.log(data.arg);
}
}
});
</script>
组件
即各种标签,组件是Vue的一大特性,可以通过定义组件来定义出具有自定义功能的标签
全局定义
可以自己定义标签,通过component
来定义,举例:
<script>
Vue.component('alert', {
// 定义一个标签类型<alert>,是一个button,点击弹窗aaa
template: '<button @click=abc>click</button>',
methods: {
abc: function() {
alert('aaa')
}
}
});
</script>
要调用时直接new就行了,举例:
<body>
<div id="app">
<alert></alert>
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script>
Vue.component('alert', {
// 定义一个标签类型<alert>,是一个button,点击弹窗aaa
template: '<button @click=abc>click</button>',
methods: {
abc: function() {
alert('aaa')
}
}
});
new Vue({
el: '#app'
// 绑定id=app的才能用这个组件
});
</script>
注:
组件当中data
属性设置问题:在组件当中如果直接往data
属性里面传入对象将会报错,比如下面代码:
<body>
<div id="app">
<alert></alert>
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script>
Vue.component('alert', {
data: {
count:1
},
// 在组件当中直接给data传入一个对象,结果会报错
template: '<button @click=abc>click</button>',
methods: {
abc: function() {
alert(this.count)
}
}
});
new Vue({
el: '#app'
});
</script>
结果会报这样的错:[Vue warn]: The "data" option should be a function that returns a per-instance value in component definitions.
,原因是在源码当中其设置了如果是一个Vue对象,那么data属性可以传入一个对象,而如果是一个组件,那么data属性必须为一个函数方法,否则所有的组件共享同样的对象,这和原来设计组件的初衷不符,因此将上面的data值改成如下函数即可:
<body>
<div id="app">
<alert></alert>
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script>
Vue.component('alert', {
data: function(){
return {count:1}
},
// data属性改成一个函数,返回需要的值
template: '<button @click=abc>click</button>',
methods: {
abc: function() {
alert(this.count)
}
}
});
new Vue({
el: '#app'
});
</script>
注2:
组件标签模板也可以在html中定义,然后通过选择器将其传给template
属性即可,举例:
<body>
<div id="app">
<alert></alert>
</div>
<template id="new-component">
<button @click="abc">aaa</button>
</template>
<!-- 在html中定义一个组件模板 -->
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script>
template = {
// 将模板封装起来
alert: {
template: '#new-component',
// 传入选择器即可获取html中定义的模板
methods: {
abc: function() {
alert('aaa')
}
}
}
};
new Vue({
el: '#app',
components: template
});
</script>
局部定义
前面那种是全局定义,所以后面new的全都包含这个组件,如果要定义局部变量就在new里面定义,举例:
<script>
new Vue({
el: '#app',
components: {
alert: {
template: '<button @click=abc>click</button>',
methods: {
abc: function() {
alert('aaa')
}
}
}
}
});
</script>>
内置组件
<component>
能够实现组件的动态绑定,通过该组件的is
属性,可以动态绑定某一组件,因此通过该插件,我们能够实现简单的分页功能,举例:
<body>
<div id="app">
<button @click=changePage('component1')>显示页面1</button>
<button @click=changePage('component2')>显示页面2</button>
<component :is=page></component>
<!-- 通过component标签的is属性绑定组件 -->
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script>
Vue.component('component1', {template: '<h1>我是页面1</h1>'});
Vue.component('component2', {template: '<h1>我是页面2</h1>'});
new Vue({
el: '#app',
data:{
page: "component1"
},
methods: {
changePage(page){
this.page = page
}
},
})
</script>
<slot>
插槽,通过该组件能使得定义的组件可以插入我们的自定义数据,举例:
<body>
<div id="app">
<page>
<div slot="header">XXX</div>
<div slot="main">YYY</div>
<!-- 调用的这个组件结果显示为:XXX\nYYY\nmore...
(header插槽内容被XXX覆盖,main插槽插入YYY,footer插槽保持默认) -->
</page>
</div>
<template id="page-tpl">
<div style="width:100px; border: 1px solid black;">
<div>
<slot name="header">aaa</slot>
<!-- 定义了3个插槽,分别用来存放自定义数据 -->
</div>
<hr>
<div>
<slot name="main"></slot>
</div>
<hr>
<div>
<slot name="footer">more...</slot>
</div>
</div>
</template>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
Vue.component("page", {
template: "#page-tpl"
})
var app = new Vue({
el: '#app',
});
</script>
<keep-alive>
缓存路由组件对象,这个在模块化开发时,结合vue-router,可以实现跳转路由时缓存<keep-alive>
包裹的组件的当前状态,而不会销毁其原来的状态,从而在下次访问时还能继续上次的操作,详细参考:
https://www.cnblogs.com/goloving/p/9256212.html
https://www.jianshu.com/p/4b55d312d297
<transition>
实现动画过渡效果的组件
更多内置组件参考
文档:https://cn.vuejs.org/v2/api/#内置的组件
https://blog.csdn.net/tangxiujiang/article/details/80144195
组件通信
如何区分父子组件
假如在A组件中调用了B组件,那么A组件就是父组件,B组件就是子组件,因此vue中的app也就是整体的父组件
父组件向子组件通信
自定义的组件如果要获取基于其生成的标签上的传值,可以通过在标签中设置属性和对应的值,其中如果设置方式为属性="值"
,那么值为字符串,如果设置方式为v-bind:属性="值"
,那么值为data或methods里对应的属性的值,然后在定义时加入props
属性,然后将绑定的变量名放在里面即可,举例:
<body>
<div id="app">
<alert aaa="111" v-bind:bbb="ccc" v-for="item in items" v-bind:item="item"></alert>
<!-- 设置aaa属性的值为111,设置bbb的值为data里的ccc的值,设置item为data里items循环的每个值,并将值传给子组件 -->
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
Vue.component('alert', {
template: '<button @click=abc>{{item}}</button>',
// 定义子组件alert,由于其调用了button,所以button相当于子组件
props: ['aaa', 'bbb', 'item'],
// 存放所有绑定或者定义的属性
// 变量名格式为:['变量名1', '变量名2', ...]
methods: {
abc: function() {
alert(this.aaa + this.bbb);
// 这里获取aaa属性和data里的ccc的值
}
}
})
new Vue({
el: '#app',
data: {
ccc: "222",
items: ['x', 'y', 'z']
}
});
</script>
从上面代码可以看出对于v-for
循环绑定的值应该循环的每一个值而不是一整个数组,因为最终你要使用的是数组里面的值
子组件向父组件通信
可以通过子组件用$emit('监听事件x', 传递值)
方法传值,父组件通过@监听事件x
来获取,举例:
<body>
<div id="app">
<alert></alert>
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
Vue.component('alertBase', {
// 定义子组件
template: '<button @click="setxxx()">click</button>',
methods: {
setxxx: function() {
this.$emit('xxx', {
// 给继承该组件的标签添加一个xxx事件,功能为传入一个对象{name:"bbb"}
name: "bbb"
});
}
}
});
Vue.component('alert', {
// 定义父组件
template: '<div><alertBase @xxx="getxxx"></alertBase>{{name}}</div>',
// 基于alertBase组件的组件
// 里面监听xxx事件,当监听到时执行getxxx方法
data: function() {
// 组件初始化时name为aaa
return {
name: "aaa"
}
},
methods: {
getxxx: function(data) {
// data是监听事件xxx获取的emit传来的值
console.log(data);
this.name = data.name;
// 此时name被改为bbb
}
}
});
new Vue({
el: '#app',
})
</script>
注:
在上面我们监听事件时只绑定了方法而没有绑定参数,这样默认接收的是事件传递的参数,假如我们自己想要传递值,又希望能够接收事件传递的参数,可以通过$event
传递事件参数,举例:
// 上面的例子中修改alert组件如下
Vue.component("alert", {
template: '<div><alertBase @xxx="getxxx($event, 1)"></alertBase>{{name}}</div>',
// 同时接收事件传递值和自定义值
data: function () {
return {
name: "aaa"
};
},
methods: {
getxxx: function (data, data2) {
// data是监听事件xxx获取的emit传来的值,data2是自定义的值,这里是1
console.log(data, data2);
this.name = data.name;
// 此时name被改为bbb
}
}
});
不同组件间传递值
当两个组件之间的关系不为父子关系时,我们可以通过设置一个公共事件对象Event = Vue()
,然后在该对象当中设置监听事件和值,然后在要接收的对象里设置mounted
属性,在里面通过监听事件对象的对应事件来获取值,举例:
<body>
<div id="app">
<send></send>
<get></get>
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var Event = new Vue();
// 定义事件对象用于收发数据
Vue.component('send', {
// 定义组件用于发送数据
template: '<button @click="setxxx()">click</button>',
methods: {
setxxx: function() {
Event.$emit("xxx", {
name: "bbb"
});
// 设置一个xxx事件,当监听到这个事件时能够获取对象{name: "bbb"}
}
}
});
Vue.component('get', {
// 定义一个组件用于接收数据
template: '<div>{{name}}</div>',
data: function() {
return {
name: "aaa"
}
},
mounted: function() {
// 通过mounted钩子函数创建方法
var _this = this;
Event.$on("xxx", function(data) {
// 监听事件当中的xxx事件
_this.name = data.name;
// 此处this是Event对象,所以使用提前定义的_this
})
}
});
new Vue({
el: '#app',
})
</script>
注:
由于我们是通过第三方Vue
进行监听,因此即使当前组件被销毁,第三方Vue
的监听事件也并不会解绑,这样造成的结果就是即使绑定监听事件的组件没了,当相关事件产生了,还是会执行对应的事件行为。为了避免这个问题,我们应该在组件销毁前将该事件解绑,举例:
beforeDestroy() {
Event.$off("xxx");
// 销毁前解绑对应事件
}
父组件调用子组件方法
子组件本身定义的方法,如果在父组件引入时想要调用,可以通过给子组件设置ref
属性,在父组件当中通过this.$ref.xxx.方法名()
调用即可
子组件调用父组件方法
- 通过
this.$parent.方法名()
调用 - 子组件发送事件,父组件监听并调用方法
- 父组件将方法直接传入子组件
组件递归
组件可以通过调用自身实现递归操作,但要注意几点:
- 被递归的组件需要设置name值,否则无法指定递归的组件对象
- 一定要有递归结束的条件,否则会导致栈溢出
这里简单实现将对象的节点通过递归进行展示,举例:
<body>
<div id="app">
<Tree :treedata="treedata"></Tree>
</div>
<template id="tree">
<div>
<ul
:treedata="treedata"
v-for="(children, node, index) in treedata"
:key="index"
>
{{node}}
<Tree v-if="children instanceof Object" :treedata="children"></Tree>
<!-- 这里进行了组件递归,当children不为对象时结束递归 -->
</ul>
</div>
</template>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
Vue.component("Tree", {
template: "#tree",
props: ["treedata"]
});
var app = new Vue({
el: "#app",
data: {
treedata: {
x: {
xx: {
xxx: {
xxxx: 1
}
}
},
y: {
yy: 2
},
z: 3
}
}
});
</script>
过滤器
通过Vue.filter('过滤器名', 过滤函数)
来定义过滤器,然后通过语法{{ 传入值 | 过滤器名 }}
调用过滤器,学过Python Web开发的会发现和jinjia2的过滤器写法很像,举例:
<body>
<div id="app">
{{ "a book" | bookname }}
<!-- 使用bookname过滤器 -->
{{ 1.213 | price(2) }}
<!-- 使用price过滤器四舍五入精确到2位小数 -->
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
Vue.filter('bookname', function(name) {
// 定义一个过滤器叫bookname,功能为给值加上书名号
return `【${name}】`;
})
Vue.filter('price', function(price, dot) {
// 定义一个过滤器叫price,功能为给值四舍五入到固定位
return price.toFixed(dot);
})
new Vue({
el: '#app'
})
</script>
从上面的price
过滤器可以看出其含有2个参数,其中默认|
左边的为传入的第一个参数,而剩下的参数则添加在调用过滤器的地方
局部过滤器
直接在Vue对象的filters
属性里面定义,举例:
<body>
<div id="app">
<span id="app">{{name | toUpper}}</span>
</div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#app',
data: {
name: 'aaa'
},
filters:{
// 定义局部过滤器
toUpper(data){
return data.toUpperCase()
}
}
});
</script>
异步通信
由于Vue自身不带有异步通信功能,而使用jQuery的ajax有点违背少操作不操作DOM的原则,因此推荐使用Axios组件,导入的CDN链接如下:
<script src="https://cdn.bootcss.com/axios/0.19.0/axios.js"></script>
简单示例
axios({
method: 'get',
url: 'http://jsonplaceholder.typicode.com/users',
data: {}
})
.then(function(response) {
console.log(response);
// 返回一个对象,包括状态码、返回内容等,其中返回内容在data属性里
})
.catch(function(error) {
console.log(error);
});
简单示例2
axios({
method: 'get',
url: 'http://jsonplaceholder.typicode.com/users',
data: {}
})
.then(response => console.log(response))
// 使用箭头函数,该语法和前面等价,即获取结果存在response里,然后执行=>右边的方法
.catch(function(error) {
console.log(error);
});
使用参考:
https://www.jianshu.com/p/7a9fbcbb1114
https://www.jianshu.com/p/13cf01cdb81f
axios和ajax、fetch区别:
https://www.jianshu.com/p/8bc48f8fde75
分割线
前面讲的都是Vue的基本使用语法和一些特性,并且是在html或js文件当中书写的代码示例。然而实际当中,真正的Vue开发往往是建立一个基于node.js的前端工程项目(即完全意义上的前后端分离项目),下面就来介绍一些关于开发Vue项目的知识点:
Vue项目开发
前端服务器:node.js(基于js的服务器)[官网]
项目模板生成:vue-cli脚手架(生成整个项目架构)[官网]
路由管理:vue-router(无刷新访问路由界面)[官网]
状态管理:vuex(管理全局数据)[官网]
界面样式:sass(css的预编译语言)[官网]
UI样式:ElementUI(提供样式组件,类似基于vue版本的bootstrap)[官网]
网站模板:vue-element-admin(提供一个网站模板)[官网]
模拟数据:mockjs(拦截ajax并生成假数据返回)[官网]
node.js
基于JS的服务器,具体内容不赘述,这里只介绍安装过程:
1.下载:http://nodejs.cn/download/
2.直接安装,然后配置下环境变量,在命令行依次输入下面命令测试是否安装成功:
node -v
# 测试node是否安装成功
npm -v
# 测试npm是否安装成功(安装node.js附带安装的包管理工具)
3.可以输入下面命令安装淘宝镜像加速从而提高下载包的速度(可选):
npm install cnpm -g
# -g代表全局安装
4.如果安装了cnpm加速,那么通过cnpm下载的命令就改为:
cnpm install 包名
# 例如:cnpm install jquery
vue-cli
官方提供的脚手架,用于快速生成Vue项目的模板,需要先安装node.js环境
特点
1.统一目录结构
2.本地调试
3.热部署(代码修改后无需刷新,页面自动改变)
4.单元测试
5.集成打包(兼容es5、es6等语法规范)
安装
在基于安装了node.js和npm的环境下,输入下面命令:
# npm install -g vue-cli
# 旧版(2.x)的安装命令,在新版本(3.x+)里已经改名为@vue/cli,安装命令如下:
npm install -g @vue/cli
安装完成后可以用vue -V
来查看是否安装成功
注:
如果要全局装,那么后面加上-g
参数,如果嫌下载慢,就改用cnpm
创建项目
通过下面命令创建:
# vue init webpack 项目名
# 旧版(2.x)的创建项目命令,一般会用webpack打包项目,如果不用则可以不加webpack
vue create 项目名
# 新版本(3.x+)的创建项目命令
# 创建当中会有很多选项,根据自己需求设置
# 注意运行命令的目录下不能有vue.js文件,否则会冲突
创建时的配置可以直接按默认创建,或者自己定制,这里给一个自己定制的方案:
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter
? Use history mode for router? (Requires proper server setup for index fallback in production) No
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with dart-sass)
? Pick a linter / formatter config: Prettier
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? (y/N) y
# 从上往下依次是:
# 选择默认模板还是自己手动配置
# 选择需要的插件(手动配置才会有的选项,通过空格选择)
# 是否使用history模式
# 选择css预处理器(在插件中勾选了css预处理器才会有该选项)
# 选择linter配置
# 选择何时进行linter检查
# 配置文件分开保存还是一起保存
# 是否将这些配置保存成模板
创建完成后进入到项目目录下,输入下面命令安装依赖:
# npm install
# 如果在文件里添加了依赖,则执行这句,否则不需要
然后可以通过下面命令运行项目:
# npm run dev
# 旧版本(2.x)的运行命令
npm run serve
# 新版本(3.x+)的运行命令
项目目录
-bulid webpack配置文件
-config webpack配置文件
-index.js 指定后台的服务端口号以及静态资源的文件夹
-node_modules 项目依赖的js
-src 源码内容
-main.js 项目入口js文件
-static 静态资源
.barbelrc babel编译文件
-index.html 主页面
-package.json webpack打包配置文件
package.json属性
- name:项目名
- version:项目版本
- description:项目描述
- author:作者
- private:是否私有
- scripts:定义一些npm命令
- dependencies:生产环境依赖
- devDependencies:开发环境依赖
- engines:引擎
- browserslist:浏览器兼容
组件编写
在脚手架搭建的项目下,默认每个组件文件(.vue
文件)无外乎就三个编写内容:
- 组件模板,举例:
<template>
...
</template>
- 导出数据,举例:
<script>
export default {
name: '...',
data () {
return {...}
}
}
</script>
- 组件样式,举例:
<style scoped>
// 如果不加scoped则是全局样式,加上则只对当前组件有效
// 要注意的是在使用vue-router时,此处样式会对子路由的组件也生效
...
</style>
当组件编写完成后,注册在components
属性里即可
项目打包和发布
1.构建打包项目:npm run build
- 通过nginx部署:
- 将dist目录放在html目录下或者随便一个最好不含中文的路径下,然后在nginx的配置文件当中进行如下配置:
location / {
root dist目录的路径(xxx/xxx/dist);
index index.html index.htm;
try_files $uri $uri/ /index.html; # 如果没有配置这句,那么当直接访问的路径不是/下的话将会是404页面
}
- 通过vue server部署:
2.安装serve
:npm install -g serve
3.启动serve
:serve dist
4.访问serve
端口(默认是5000)即可
vue-cli官方文档
https://cli.vuejs.org/zh/guide/
vue-router
负责vue的路由管理,能够实现更新视图却不用刷新界面
安装
npm install vue-router
简单示例
1.在components
文件夹下新建文件Login.vue
,编写代码如下:
<template>
<div>
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'login',
data () {
return {
msg: 'this is login page'
}
}
}
</script>
2.在router
文件夹下新建index.js
文件,编写代码如下:
import Vue from 'vue'
import Router from 'vue-router'
// 导入vue-router
import Login from '../components/Login'
// 导入Login.vue
Vue.use(Router);
// 使用vue-router
export default new Router({
routes: [
// 存放着所有路由的数组
{
path: '/login',
// 路由地址
name: 'login',
component: Login
// 代表使用Login.vue组件
}
]
})
3.修改main.js
代码如下:
import Vue from 'vue';
import App from './App';
import VueRouter from 'vue-router'
// 导入vue-router
import router from './router'
// 使用router目录下的vue-router对象
Vue.use(VueRouter)
// 使用vue-router
new Vue({
el: '#app',
router,
// 传入router目录下的vue-router对象,此时就可以使用this.$router对象
render: h => h(App),
});
4.修改App.vue
如下:
<template>
<div id="app">
<router-link to="/">首页</router-link>
<!-- 设置路由的超链接,类似<a href="/">...</a> -->
<router-link to="/login">登录页</router-link>
<router-view></router-view>
<!-- 显示对应路由视图的内容 -->
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
5.在命令行输入命令:npm run dev
运行项目
注:
默认vue-router
运行时的路由模式是按照hash模式,即路径开头会有/#/
标识符,代表访问当前页下的哪个路径,如果不希望有/#/
标识符,可以在定义Router
时添加一个mode: 'history'
即可,举例:
export default new Router({
mode: 'history',
// 设置为history模式
routes: [
...
]
})
其中history
模式和hash
模式区别参考:https://blog.csdn.net/lyn1772671980/article/details/80804419
history
模式下服务器/后端对应配置参考:https://www.cnblogs.com/mica/p/10876822.html
注2:
定义路由时,其路径是支持字符串匹配的,举例:
{
path: '/aaa/*',
// 匹配aaa下所有路由,但优先级低
name: 'hello',
component: Hello
}
注3:
Vue.use()
功能和原理参考:
https://www.cnblogs.com/fps2tao/p/10830804.html
https://blog.csdn.net/Fabulous1111/article/details/88696006
子路由
前面的示例当中都是一级路由,如果需要设置一个能够基于父路由模板的子路由,则可以在父级路由当中设置children
属性,然后配置路由信息。例如将上面示例中/login
页面放在/main
页面下,则修改index.js
如下:
import Vue from 'vue'
import Router from 'vue-router'
// 导入vue-router
import Login from '../components/Login'
// 导入Login.vue
import Main from '../components/Main'
// 导入Main.vue
Vue.use(Router);
// 使用vue-router
export default new Router({
routes: [{
path: '/main',
name: 'main',
component: Main,
children: [
// 设置子路由,子路由的内容在main的<router-view>标签里显示
{
path: '/main/login',
// 登录路由地址
name: 'login',
component: Login
}
]
},
// 下面这个虽然路径看起来也是子路由,但视图的展示并不基于父路由
// {
// path: '/main/login',
// // 登录路由地址
// name: 'login',
// component: Login
// }
]
})
然后在main.vue
当中添加<router-view>
标签存放子路由内容,举例:
<template>
<div>
<h1>{{ msg }}</h1>
<router-view></router-view>
<!-- 添加视图显示,用于存放子路由内容 -->
</div>
</template>
<script>
export default {
name: 'main',
data () {
return {
msg: 'this is main page'
}
}
}
</script>
此时打开页面即可发现父路由和子路由的内容都一同显示
路由传参
常用的路由传参数方法有两种:
1.通过路由/:参数名
传参(多个参数就用&隔开,例如:路由/:参数1&:参数2
),然后接收方通过{{ $route.params.参数名 }}
获取,举例:
路由定义:
{
path: '/login/:id&:name',
// 登录路由地址,并且传入id和name参数
name: 'login',
component: Login
}
访问路由:
<router-link to="/login/1&2">登录页</router-link>
接收方:
{{ $route.params.id }}
{{ $route.params.name}}
2.访问路由改为传入一个对象(需要v-bind
绑定to
属性),根据路由的name
绑定对应路由,参数放在params
属性当中,举例(路由定义和接收方与前面相同):
访问路由:
<router-link :to="{name:'login', params:{id:1, name:'aaa'}}">登录页</router-link>
<!-- to设置为绑定属性,并绑定name为login的路由,并传两个参数进去 -->
路由接收get参数
对于get请求传来的参数(如:http://xxx/?参数1=xxx&参数2=yyy&...
),可以通过{{ $route.query.参数名 }}
获取
路由传属性
前面介绍的路由传参是通过$route.params.参数
调用获取内容,我们还可以通过props
属性接收路由传递的参数,此时需要在路由配置当中添加属性props:true
代表接收属性,然后在对应的组件文件当中添加要获取的属性名,传参方法依然是上面的方式即可,举例:
路由定义:
{
path: '/login/:id&:name',
// 登录路由地址,并且传入id参数
props: true,
// 允许传入属性
name: 'login',
component: Login
}
接收方:
<template>
<div>
<h1>{{ msg }}</h1>
{{ id }}
{{ name }}
<!-- 直接使用props接收到的属性 -->
</div>
</template>
<script>
export default {
name: 'login',
props: ["id", "name"],
// 定义接收的属性
data () {
return {
msg: 'this is login page'
}
}
}
</script>
组件重定向
组件相同,但路径不同可以通过重定向实现,举例:
{
path: '/regist',
redirect: '/login'
// 重定向到/login
}
路由属性
$route
路由对象,代表当前路由,里面存放了一些路由数据信息,这里列举几个常用属性(可以在vue-dev调试工具里查看该对象下内置的属性方法等):
params 接收传递的参数
query 接收get请求传递的参数
meta 自定义一些标签属性,比如设置一个bool属性判断某个组件在当前路由下是否显示
path 当前路径
$router
路由器对象,这主要用于操作路由导航,这里列举几个常用方法:
back() 路由回退
push() 路由跳转,可以回退的
replace() 路由跳转,无法回退
go() 路由前进/回退多少步
详细可以参考文档:https://router.vuejs.org/zh/guide/essentials/navigation.html
路由钩子
vue-router提供了一系列钩子函数和属性使得可以在进入、离开路由等期间执行对应的操作,下面列举一些:
钩子函数:
-
beforeEach
:进入所有路由前 -
afterEach
:进入所有路由后
使用举例:
router = new VueRouter({...})
router.beforeEach(function(to, from, next) {
// 传入三个对象参数:前往的路由,前一个路由,以及执行的功能函数(afterEach里没有next参数)
// 可以根据对象内的属性,比如params(参数)、path(路径)等判断接下来的操作
next();
// 进入下一个路由,也可以往方法里传参,比如:next('/')-跳转到/路径下
})
钩子属性:
-
beforeRouteEnter
:进入路由前执行 -
beforeRouteLeave
:离开路由前执行
使用举例:
<template>
<div>
<h1>{{ msg }}</h1>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'main',
data () {
return {
msg: 'this is main page'
}
},
beforeRouteEnter:(to, from, next) => {
console.log("进入路由前...");
}
}
</script>
路由缓存
两种方案:
- 使用内置的
<keep-alive>
组件 - 在对应的路由里设置
meta
属性:添加keepAlive:true
,然后在组件当中进行逻辑判断,举例:
<keep-alive>
<!-- keep-alive为true时 -->
<router-view :key="key" v-if="$route.meta.keepAlive" />
</keep-alive>
<!-- keep-alive为false时 -->
<router-view :key="key" v-if="!$route.meta.keepAlive" />
路由懒加载
对于整个vue工程,默认情况下会将所有模块打包成1个js文件,此时在访问界面时会一次性引入所有的组件模块,即一个特别大的js文件,这个时候可能就会十分消耗访问页面的时间,而且也没必要,毕竟我们一般访问不到所有的路由。于是我们可以通过路由懒加载功能,当第一次访问特定路由的时候才去引入对应的组件,从而减少不必要的开销,实现的方法也很简单,对于那些大的组件(特别是一级路由下的),可以将原来通过import
关键字导入的方式改成通过import()
函数按需加载,举例:
// import Login from '../view/Login'
// 原来导入Login模块的方式
const Login = () =>import('../view/Login')
// 改成通过import函数返回,此时只有在需要用到该组件的路由时才会执行这句导入Login模块
详细参考:
https://blog.csdn.net/qq_38614249/article/details/79468609
https://blog.csdn.net/weixin_42420559/article/details/88706631
异步请求
1.通过下面语句导入axios
:
import axios from 'axios'
Vue.prototype.axios = axios;
2.通过this.axios
调用axios
请求
异步请求封装
在模块化开发中,我们往往会将异步请求进行封装,而其中大部分的配置都是相似的,因此此时我们可以先通过create
方法对请求进行封装后,再用封装后的axios进行请求,举例:
import axios from "axios";
import qs from "qs";
const config = {
// 配置url前缀,如果传入的url不是http开头就会加上该前缀
baseURL: "http://127.0.0.1:6666/",
// timeout时间
timeout: 5 * 1000,
// CORS跨域是否需要携带资源凭证,配置了该项后端才能获取到cookie
// 但要求后台配置Access-Control-Allow-Origin为当前源地址,且Access-Control-Allow-Credentials为true
withCredentials: true,
// 跨域请求
crossDomain: true,
// params参数处理
paramsSerializer: params => qs.stringify(params, { indices: false }),
// data请求数据处理
transformRequest: [data => qs.stringify(data, { indices: false })],
// 成功请求状态码判断([200, 400)都算成功)
validateStatus: status => status >= 200 && status < 400,
// 流数据传输
responseType: "blob",
// 自定义请求头
headers: {},
// 参数
params: {},
// 数据
data: {}
// ...
};
const _axios = axios.create(config);
// 基本配置
_axios.interceptors.request.use(
// 请求拦截器
config => {
// 若token存在则添加token
const token = window.localStorage.getItem("token");
token && (config.headers.Authorization = token);
return config;
},
error => {
return Promise.error(error);
}
);
_axios.interceptors.response.use(
// 响应拦截器
response => {
// 成功请求直接返回
return response;
},
error => {
// 失败请求判断
if (error.response) {
switch (error.response.status) {
// 401: 未登录
case 401:
alert("身份验证失败");
break;
// 403:token过期
case 403:
alert("登录过期");
// 清除token
break;
// 404:请求不存在
case 404:
alert("网页不存在");
break;
// 其他错误
default:
alert(error.response.data.message);
}
return Promise.reject(error.response);
} else {
if (!window.navigator.onLine) {
// 网络连接失败处理
alert("网络连接失败");
}
return Promise.reject(error);
}
}
);
export function post(url, params = {}, data = {}) {
return _axios({
method: "post",
url,
params,
data
});
}
export function get(url, params = {}) {
return _axios({
method: "get",
url,
params
});
}
export function put(url, params = {}, data = {}) {
return _axios({
method: "put",
url,
params,
data
});
}
export function _delete(url, params = {}) {
// delete是js关键字,所以前面加了个_
return _axios({
method: "delete",
url,
params
});
}
export default _axios;
更多参考:
https://www.bbsmax.com/A/mo5kZbnJwR/
https://www.kancloud.cn/yunye/axios/234845
vue-router官方文档
vuex
负责Vue开发当中的状态管理模式,比如设置公共变量,保持多个组件之间使用一致的数据
安装
npm install vuex
使用步骤
1.安装vuex
2.import
导入vuex
,并通过Vue.use(vuex)
载入vuex
3.创建一个vuex.Store
对象,配置常用属性
4.添加到Vue
对象当中
5.在组件当中通过import {mapState} from 'vuex'
获取state
(同理通过mapMutations
获取mutations
、通过mapActions
获取actions
、通过mapGetters
获取getters
)
Vuex.Store属性
-
state
:全局状态,存放各种属性 -
mutations
:对属性的操作,这里面定义的函数会传入state
属性,并且只有通过该属性里的方法操作属性才有状态改变记录 -
actions
:也是对属性的操作(但不是直接操作,而是通过调用mutations
里的函数),这里面定义的函数会传入context
上下文参数,可以在上下文中通过commit('函数名')
执行mutations
里对应的函数,一般简单的操作放在mutations
里,复杂放actions
里 -
getters
:获取属性,这里面定义的函数会传入state
属性,然后可以读取state
里面的数据后根据需求返回内容,并且每当state
里的数据被操作以后都会执行 -
modules
:模块,可以将多个模块文件里配置上面的属性后,一起放在主vuex文件的modules
属性里导入配置
注:
在vuex中,可以使用commit
调用mutations
里的方法,也可以使用this.$store.dispatch()
调用actions
里的方法,前者只能同步,而后者可以异步操作,可以参考:
https://www.jb51.net/article/147581.htm
辅助函数
-
mapState
:返回Store
对象里的state
属性对象,传入字符串数组,字符串为需要获取的属性名 -
mapMutations
:同理 -
mapActions
:同理 -
mapGetters
:同理
5个属性和辅助函数详细参考
https://blog.csdn.net/wh710107079/article/details/88181015
简单示例
- store/index.js(配置vuex属性)
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const store = new Vuex.Store({
// 定义Store对象
state: {
count: 0,
// 设置一个count属性,实现一个计算加减法工具
},
mutations: {
add(state) {
// mutations里传入state属性,并对其进行基本操作
state.count++;
},
sub(state) {
state.count--;
}
},
actions: {
add2(context) {
// actions里传入上下文,通过commit方法传入要执行的函数
context.commit('add');
context.commit('add');
// alert(context.state.count);
// 上下文对象里存放着state,所以也可以直接获取state的数据
}
},
getters: {
getCount(state) {
// getters里传入state属性,state里的值每操作一次,这里都会运行一次
console.log(state.count);
// state的值一被操作,这里就输出
return `result:${state.count}`;
// 返回数据
}
}
})
export default store;
- main.js(配置vue)
...
import store from './store'
new Vue({
el: '#app',
...
store,
// 传入store对象,此时就可以使用this.$store对象
render: h => h(App),
});
- xxx.vue(获取vuex属性)
<template>
<div>
<h1>{{ count }}</h1>
<!-- 也可以使用this.$store.state.count替换 -->
<button @click="add">add</button>
<button @click="sub">sub</button>
<button @click="add2">add2</button>
<button @click="showGetCount">showGetCount</button>
</div>
</template>
<script>
import {mapState, mapMutations, mapActions, mapGetters} from 'vuex'
// 导入vuex下几个属性对象
export default {
name: 'App',
computed: {
...mapState(["count"])
// 获取state里的count属性,并将其结构
},
methods: {
...mapMutations(['add', 'sub']),
// 同理将对应的方法结构
// 也可以改成:add: function(){this.$store.commit("add")},
...mapActions(['add2']),
...mapGetters(['getCount']),
// getter的内容也可以放在computed里直接使用
showGetCount: function(){
// getters里的方法只能返回内容,因此通过this.$store.getters调用获取
console.log(this.$store.getters.getCount);
}
}
}
</script>
vuex日志打印配置
import Vuex from "vuex";
import logger from "vuex/dist/logger";
Vue.use(Vuex);
const debug = process.env.NODE_ENV !== "production";
const store = new Vuex.Store({
state,
...
plugins: debug ? [logger()] : []
});
export default store;
vuex官方文档
https://vuex.vuejs.org/zh/guide/
页面刷新导致vuex状态消失解决
1.根组件监听页面刷新事件,将数据存储本地
export default {
name: "app",
created() {
//在页面刷新时将vuex里的信息保存到localStorage里
window.addEventListener("beforeunload", () => {
localStorage.setItem("messageStore", JSON.stringify(this.$store.state));
});
//在页面加载时读取localStorage里的状态信息
localStorage.getItem("messageStore") &&
this.$store.replaceState(
Object.assign(
this.$store.state,
JSON.parse(localStorage.getItem("messageStore"))
)
);
localStorage.removeItem("messageStore");
}
};
2.使用插件vuex-persistedstate
安装:npm install vuex-persistedstate --save
使用示例:
import createPersistedState from "vuex-persistedstate"
const store = new Vuex.Store({
// ...
plugins: [createPersistedState()]
})
3.重载页面时向后端进行相应数据请求
参考:
https://www.jb51.net/article/160918.htm
https://www.jianshu.com/p/c2078f6f63b3
ElementUI
提供了很多样式界面布局组件,功能和bootstrap类似,只不过bootstrap是基于jQuery,而ElementUI基于vue组件,在结合vue开发时可以作为替代bootstrap定义样式的方案
安装
npm i element-ui -S
ElementUI官方文档
https://element.eleme.cn/#/zh-CN/component/installation
Vue-element-admin
是基于Vue+ElementUI提供的网站模板
Vue-element-admin官方文档
https://panjiachen.github.io/vue-element-admin-site/zh/guide/
mockjs模拟数据
使用步骤:
- 安装mockjs:
npm install mockjs
- 导入mockjs模块,并编写假数据接口
import Mock from 'mockjs'
Mock.mock("/a", {
"user": "aaa"
});
export default Mock
- 在main.js中导入mock的假数据接口:
import mock from '@/mock/index'
- ajax访问对应的假数据接口(在network里可以发现没有发起网络请求,请求被拦截并返回了假数据)
参考:https://www.cnblogs.com/vickya/p/8568447.html
Vue加密参考
md5加密
可以使用blueimp-md5
或js-md5
,都是专门提供md5加密的轻量级框架,安装参考:
npm install --save js-md5
npm install --save blueimp-md5
使用举例:
// ----------------------------------------
// vue项目引用举例
import md5 from "blueimp-md5";
import Vue from "vue";
Vue.prototype.$md5 = md5;
// ----------------------------------------
// 使用举例
let val = md5("123");
crypto
是一个提供了各种加密工具的库
Vue当中XSS防范参考
https://blog.csdn.net/qiumen/article/details/88119275
Vue面试题总结
https://www.jianshu.com/p/182a67f4b254
MVVM实现
原理:数据劫持+发布订阅
原型实现
// Vue对象初始化配置
function MyVue(option = {}) {
// 将所有数据挂载到option中
this.$option = option;
let data = this._data = this.$option.data;
// 观察对象
observe(data);
// 对所有数据绑定动态操作和访问功能
for (let key in data) {
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
get() {
return this._data[key];
},
set(val) {
this._data[key] = val;
}
})
}
// 绑定computed属性
initComputed.call(this);
// 模板编译,对vue绑定区域进行数据编译
new Compile(this.$option.el, this);
}
// computed属性解析实现
function initComputed() {
let vm = this;
let computed = this.$option.computed;
Object.keys(computed || {}).forEach(key => {
Object.defineProperty(vm, key, {
get:typeof computed[key] === "function"? computed[key]: computed[key].get,
set() {}
})
})
}
// 模板编译实现
function Compile(el, vm) {
vm.$el = document.querySelector(el);
let fragment = document.createDocumentFragment();
// 获取所有vue绑定区域的子元素
while (child = vm.$el.firstChild) {
fragment.appendChild(child);
}
// 对所有子元素进行编译替换
replace(fragment);
// 编译替换实现
function replace(fragment) {
Array.from(fragment.childNodes).forEach(node => {
// 获取子节点内容
let text = node.textContent;
// 匹配{{xxx}}格式内容
let reg = /\{\{(.*)\}\}/;
// 如果是文本元素
if (node.nodeType === 3 && reg.test(text)) {
let arr = RegExp.$1.split(".");
let val = vm;
// 遍历到指定数据节点,如:x.y,则遍历到y的值
arr.forEach(k => (val = val[k]));
// 添加监听对象,监听数据变化
new Watcher(vm, RegExp.$1, newVal => {
node.textContent = text.replace(reg, newVal);
})
// 替换节点文本
node.textContent = text.replace(reg, val);
}
// 如果是标签元素
if (node.nodeType === 1) {
// 获取标签属性
let nodeAttrs = node.attributes;
Array.from(nodeAttrs).forEach(attr => {
let name = attr.name;
let exp = attr.value;
// 解析执行,这里解析v-model
if(name.indexOf("v-") === 0) {
node.value = vm[exp];
}
// 添加监听者监听标签变化
new Watcher(vm, exp, newVal => {
node.value = newVal;
});
// 添加input监听事件
node.addEventListener("input", function(e) {
let newVal = e.target.value;
vm[exp] = newVal;
})
})
}
// 替换成编译后的节点
if (node.childNodes) {
replace(node);
}
})
}
// 将vue绑定的区域替换成编译后的结果
vm.$el.appendChild(fragment);
}
// 观察者对象
function Observe(data) {
// 创建订阅对象绑定所有监听内容
let dep = new Dep();
for (let key in data) {
let val = data[key];
observe(val);
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
// 添加订阅
Dep.target && dep.addSub(Dep.target);
return val;
},
set(newVal) {
if (val === newVal) return;
val = newVal;
// 修改值时需要将修改的值也变为观察者对象
observe(newVal);
// 发布通知,更新所有订阅的监听者
dep.notify();
}
})
}
}
// 观察者方法,递归设置属性
function observe(data) {
if (typeof data !== "object") return;
return new Observe(data);
}
// 订阅者
function Dep() {
this.subs = [];
}
// 添加订阅方法
Dep.prototype.addSub = function(sub) {
this.subs.push(sub);
}
// 通知所有订阅的监听者更新视图
Dep.prototype.notify = function() {
this.subs.forEach(sub => sub.update());
}
// 观察者对象
function Watcher(vm, exp, fn) {
// 绑定对应元素
this.vm = vm;
this.exp = exp;
this.fn = fn;
Dep.target = this;
let val = vm;
let arr = exp.split(".");
arr.forEach(k => {
val = val[k];
});
Dep.target = null;
}
// 观察者更新方法
Watcher.prototype.update = function() {
let val = this.vm;
let arr = this.exp.split(".");
arr.forEach(k => {
val = val[k];
})
this.fn(val);
}
面向对象实现
// Vue对象初始化配置
class MyVue {
constructor(option = {}) {
// 将所有数据挂载到option中
this.$option = option;
// 绑定data属性
this.initData();
// 绑定computed属性
this.initComputed();
// 模板编译,对vue绑定区域进行数据编译
new Compile(this.$option.el, this);
}
// data属性解析实现
initData() {
let data = (this._data = this.$option.data);
// 观察对象
Observe.observe(data);
// 对所有数据绑定动态操作和访问功能
for (let key in data) this.defineDataObserve(key);
}
// computed属性解析实现
initComputed() {
let computed = this.$option.computed;
Object.keys(computed || {}).forEach(key =>
this.defineComputedObserve(computed, key)
);
}
// 定义data对象属性
defineDataObserve(key) {
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
get() {
return this._data[key];
},
set(val) {
this._data[key] = val;
}
});
}
// 定义computed对象属性
defineComputedObserve(computed, key) {
Object.defineProperty(this, key, {
get:
typeof computed[key] === "function" ? computed[key] : computed[key].get,
set() {}
});
}
}
// 模板编译对象
class Compile {
constructor(el, vm) {
this.vm = vm;
this.vm.$el = document.querySelector(el);
// 获取所有vue绑定区域的子元素
let fragment = this.getFragment();
// 对所有子元素进行编译替换
this.replace(this.vm, fragment);
// 将vue绑定的区域替换成编译后的结果
this.vm.$el.appendChild(fragment);
}
// 获取所有vue绑定区域的子元素实现
getFragment() {
let fragment = document.createDocumentFragment();
let child = null;
while ((child = this.vm.$el.firstChild)) {
fragment.appendChild(child);
}
return fragment;
}
// 编译替换实现
replace(vm, fragment) {
Array.from(fragment.childNodes).forEach(node => {
// 如果是文本元素
if (node.nodeType === 3) this.compileText(vm, node);
// 如果是标签元素
if (node.nodeType === 1) this.compileLabel(vm, node);
// 替换成编译后的节点
if (node.childNodes) this.replace(vm, node);
});
}
// 文本元素编译
compileText(vm, node) {
// 匹配{{xxx}}格式正则
let reg = /\{\{(.*)\}\}/;
if (!reg.test(node.textContent)) return;
// 获取子节点内容
let text = node.textContent;
let arr = RegExp.$1.split(".");
let val = vm;
// 遍历到指定数据节点,如:x.y,则遍历到y的值
arr.forEach(k => (val = val[k]));
// 添加监听对象,监听数据变化
new Watcher(vm, RegExp.$1, newVal => {
node.textContent = text.replace(reg, newVal);
});
// 替换节点文本
node.textContent = text.replace(reg, val);
}
// 标签元素编译
compileLabel(vm, node) {
// 获取标签属性
let nodeAttrs = node.attributes;
Array.from(nodeAttrs).forEach(attr => {
let name = attr.name;
let exp = attr.value;
// 解析执行,这里解析v-model
if (name.trim() === "v-model") {
node.value = vm[exp];
}
// 添加监听者监听标签变化
new Watcher(vm, exp, newVal => {
node.value = newVal;
});
// 添加input监听事件
node.addEventListener("input", function (e) {
let newVal = e.target.value;
vm[exp] = newVal;
});
});
}
}
// 观察者对象
class Observe {
constructor(data) {
// 创建订阅对象绑定所有监听内容
let dep = new Dep();
for (let key in data) this.defineObserve(data, key, dep);
}
// 定义观察者属性
defineObserve(data, key, dep) {
let val = data[key];
Observe.observe(val);
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
// 添加订阅
Dep.target && dep.addSub(Dep.target);
return val;
},
set(newVal) {
if (val === newVal) return;
val = newVal;
// 修改值时需要将修改的值也变为观察者对象
Observe.observe(newVal);
// 发布通知,更新所有订阅的监听者
dep.notify();
}
});
}
// 观察者方法,递归设置属性
static observe(data) {
if (typeof data !== "object") return;
return new Observe(data);
}
}
// 订阅者对象
class Dep {
constructor() {
this.subs = [];
}
// 添加订阅
addSub(sub) {
this.subs.push(sub);
}
// 通知所有订阅的监听者更新视图
notify() {
this.subs.forEach(sub => sub.update());
}
}
// 观察者对象
class Watcher {
constructor(vm, exp, fn) {
// 绑定对应元素
this.vm = vm;
this.exp = exp;
this.fn = fn;
this.bindDeco(this.bindWatcher)(this);
}
// 绑定方法
bindWatcher() {
let val = this.vm;
let arr = this.exp.split(".");
arr.forEach(k => {
val = val[k];
});
}
// 绑定装饰器
bindDeco(fn) {
let deco = (proto, ...args) => {
Dep.target = this;
fn.call(proto, ...args)
Dep.target = null;
}
return deco;
}
// 观察者更新方法
update() {
let val = this.vm;
let arr = this.exp.split(".");
arr.forEach(k => {
val = val[k];
});
this.fn(val);
}
}