Vue基础知识点,包括常用指令、计算属性、侦听属性等。

Vuejs响应式原理

1
2
3
编译组件:对特殊标记的部分(比如双大括号部分)进行替换为相应的数据值。
收集依赖:对于编译阶段依赖的数据进行监听(这个都是通过 watcher 对象实现的)
通知更新:当步骤2中监听的数据发生变化时,会通知 watcher 进行重新计算,触发关联视图更新。

vue优点

轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十kb;

简单易学:国人开发,中文文档,不存在语言障碍 ,易于理解和学习;

双向数据绑定:保留了angular的特点,在数据操作方面更为简单;

组件化:保留了react的优点,实现了html的封装和重用,在构建单页面应用方面有着独特的优势;

视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;

虚拟DOM:dom操作是非常耗费性能的,不再使用原生的dom操作节点,极大解放dom操作,但具体操作的还是dom不过是换了另一种方式;

运行速度更快: 相比较与react而言,同样是操作虚拟dom,就性能而言,vue存在很大的优势。

vue生命周期

在beforeCreate阶段,vue实例的挂载元素el和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了,el和数据对象data都为undefined,还未初始化。

载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。

更新前/后:当data变化时,会触发beforeUpdate和updated方法

销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在

vue组件中data必须是一个函数?

官网上是这样写的:

一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝,如果 Vue 没有这条规则,可能会影响到其它所有实例。

当创建Vue实例时,data必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。

v-if和v-show有什么区别?

v-if和v-show看起来似乎差不多,当条件不成立时,其所对应的标签元素都不可见,但是这两个选项是有区别的:

1、v-if在条件切换时,会对标签进行适当的创建和销毁,而v-show则仅在初始化时加载一次,因此v-if的开销相对来说会比v-show大。

2、v-if是惰性的,只有当条件为真时才会真正渲染标签;如果初始条件不为真,则v-if不会去渲染标签。v-show则无论初始条件是否成立,都会渲染标签,它仅仅做的只是简单的CSS切换。

computed和watch的区别

计算属性computed:

支持缓存,只有依赖数据发生改变,才会重新进行计算不支持异步,当computed内有异步操作时无效,无法监听数据的变化computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。

侦听属性watch:

不支持缓存,数据变,直接会触发相应的操作;watch支持异步;监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;当一个属性发生变化时,需要执行对应的操作;一对多;监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数:
immediate:组件加载立即触发回调函数执行

1
2
3
4
5
6
7
8
9
watch: {
firstName: {
handler(newName, oldName) {
this.fullName = newName + ' ' + this.lastName;
},
// 代表在wacth里声明了firstName这个方法之后立即执行handler方法
immediate: true
}
}

deep: deep的意思就是深入观察,监听器会一层层的往下遍历,给对象的所有属性都加上这个监听器,但是这样性能开销就会非常大了,任何修改obj里面任何一个属性都会触发这个监听器里的 handler

1
2
3
4
5
6
7
8
9
watch: {
obj: {
handler(newName, oldName) {
console.log('obj.a changed');
},
immediate: true,
deep: true
}
}

优化:我们可以使用字符串的形式监听

1
2
3
4
5
6
7
8
9
watch: {
'obj.a': {
handler(newName, oldName) {
console.log('obj.a changed');
},
immediate: true,
// deep: true
}
}

这样Vue.js才会一层一层解析下去,直到遇到属性a,然后才给a设置监听函数。

$nextTick是什么?

vue实现响应式并不是数据发生变化后dom立即变化,而是按照一定的策略来进行dom更新。
nextTick 是在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后使用nextTick,则可以在回调中获取更新后的 DOM

v-for key的作用

当Vue用 v-for 正在更新已渲染过的元素列表是,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue将不是移动DOM元素来匹配数据项的改变,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。
为了给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。key属性的类型只能为 string或者number类型。

key 的特殊属性主要用在Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes。如果不使用 key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。使用key,它会基于key的变化重新排列元素顺序,并且会移除 key 不存在的元素。

v-for提升性能的原因:

key相同时,两个VNode会相同,可以避免不必要的DOM更新;而且在diff内部,也会根据key来跟踪VNode。

双向数据绑定原理是什么?

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

主要分为以下几个步骤:

1、需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化

2、compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

3、Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
①在自身实例化时往属性订阅器(dep)里面添加自己
②自身必须有一个update()方法
③待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。

4、MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

vue-router路由页面管理

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。

包含的功能有:

1、嵌套的路由/视图表

2、模块化的、基于组件的路由配置

3、路由参数、查询、通配符

4、基于 Vue.js 过渡系统的视图过渡效果

5、细粒度的导航控制

6、带有自动激活的 CSS class 的链接

7、HTML5 历史模式或 hash 模式,在 IE9 中自动降级

8、自定义的滚动条行为

1、动态路由匹配

例如:对不同ID的用户,使用同一个组件来渲染。

1
2
3
4
5
6
7
8
9
const User = {
template: "<div>User</div>"
}
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
]
})

这样/user/foo 和 /user/bar 都将映射到相同的路由User。

一个“路径参数”使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用。于是,我们可以更新 User 的模板,输出当前用户的 ID:

1
2
3
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}

2、编程式的导航
router.push({name:””,params:{}})

1
2
3
4
5
6
7
8
1this.$router.push
// 带参数
this.$router.push({
name:"User",
params:{
userId:"123"
}
})

router.push({path:””,query:””})

1
2
3
4
5
6
(2) this.$router.push({
path:"/user",
query:{
plan:"private"
}
})

router.replace()使用方法跟router.push()很像,不会向history添加新纪录,替换掉当前的history记录。
router.go()在history记录中向前或者向后多少步。正数为向前多少步,负数为向后多少步

3、滚动行为

使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。

注意:这个功能只在支持 history.pushState 的浏览器中可用。

1
2
3
4
5
6
7
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}

4、路由懒加载

有时候我们想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只需要使用命名chunk,一个特殊的注释语法来提供chunk name(需要 Webpack > 2.4)。

1
2
3
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')

表单输入控制

表单修饰符:如果是简单的控制输入一定是数字或者去掉用户输入的收尾空白符,可以直接使用 Vue 提供的表单修饰符 .number 和 .trim

1、如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:

1
<input v-model.number="age" type="number">

2、如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

1
<input v-model.trim="msg">

change事件:给表单绑定事件,在事件处理中进行表单输入控制

1
<input v-model="value2" type="text" @change="inputChange(value2)" />
1
2
3
4
5
6
7
methods: {
inputChange: function(val) {
if (!val) return ''
val = val.toString()
this.value2 = val.charAt(0).toUpperCase() + val.slice(1)
}
}

filter过滤器

1
<input v-model="value1"  type="text" />
1
2
3
4
5
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
1
2
3
4
5
watch: {
value1(val) {
this.value1 = this.$options.filters.capitalize(val);
}
}

指令:声明一个全局指令

点击展示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 只能输入正整数,0-9的数字
Vue.directive('enterIntNumber', {
inserted: function (el) {
let trigger = (el, type) => {
const e = document.createEvent('HTMLEvents')
e.initEvent(type, true, true)
el.dispatchEvent(e)
}
el.addEventListener("keyup", function (e) {
let input = e.target;
let reg = new RegExp('^\\d{1}\\d*$'); //正则验证是否是数字
let correctReg = new RegExp('\\d{1}\\d*'); //正则获取是数字的部分
let matchRes = input.value.match(reg);
if (matchRes === null) {
// 若不是纯数字 把纯数字部分用正则获取出来替换掉
let correctMatchRes = input.value.match(correctReg);
if (correctMatchRes) {
input.value = correctMatchRes[0];
} else {
input.value = "";
}
}
trigger(input, 'input')
});
}
});
1
2
<!--限制输入正整数-->
<input v-enterIntNumber placeholder="0" type="number">

Vue渲染目标元素的6种方法

1、直接创建Vue实例

点击展示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>vue</title>
</head>

<body>
<div id="app"></div>
<!-- 这里直接引入cdn源码 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
var app = new Vue({
el: "#app",
template: "<h1>{{message}}</h1>",
data(){
return{
message:'Hello Vue.js!'
}
}
});
</script>
</body>

</html>

2、Vue.extend

Vue.extend(options) 方式是使用Vue构造器的一个“子类”,其参数同Vue(options)一模一样,唯一的不同是没有 el 属性来指定挂载的DOM元素,所以这里需要通过 $mount() 方法,来手动实现挂载。

点击展示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="app"></div>
<script>
var app = Vue.extend({
el: "#app",
template: '<h1>{{msg}}</h1>',
data() {
return {
msg: 'hello extend'
}
}
})
// 注意这里 Vue.extend 方式是生成了一个 Vue 子类,所以需要 new关键字来重新创建,然后手动挂载。
new app().$mount('#app');
</script>

3.Vue.component

Vue.component(id, [definition])方式是注册一个名称为id的全局组件,然后我们可以通过使用该组件来,实现目标元素渲染。其中definition 参数同 Vue.extend 中的参数一模一样,方法一样,需要使用$mount()方法手动挂载。

点击展示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<div id="app"></div>
<script>
// var app = Vue.component('hello', {
// template:'<h1>{{msg}}</h1>',
// data() {
// return {
// msg: 'hello component'
// }
// }
// })
// new app().$mount('#app')
//1、 注册组件
Vue.component('hello', {
template: '<h1>{{msg}}</h1>',
data() {
return {
msg: 'hello component'
}
}
})
// 2、创建Vue实例
new Vue({
el: "#app",
template: '<hello />'
})
</script>

仅仅注册组件式不够的,我们还要通过创建一个Vue实例,才能使用该组件。

4、Vue.directive自定义指令

点击展示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="app">
<div v-hello="msg"></div>
</div>
<script>
Vue.directive('hello', {
bind: function(el, binding) {
el.innerHTML = "<h1>" + binding.value + "</h1>"
}
})
new Vue({
el: "#app",
data() {
return {
msg: "hello directive 自定义指令"
}
}
})
</script>

5、Vue.compile

Vue.compile(template) 参数也就是 template 模板字符串属性,然后通过替换 Vue实例的 render 函数,来实现渲染。

点击展示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="app"></div>
<script>
// 参数就是template模板字符串 然后通过替换Vue实例的render函数来实现渲染
var tpl = Vue.compile('<h1>{{msg}}<h1>')
new Vue({
el: "#app",
data() {
return {
msg: "hello,compile"
}
},
render: tpl.render
})
</script>

6、render

Vue实例在创建的过程中也会调用 render 函数,render 函数默认会传递一个参数,我们可以通过 createElement 来动态创建一个 VNode,以此来渲染目标元素

点击展示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="app"></div>
<script>
new Vue({
el: "#app",
data() {
return {
msg: 'hello,render'
}
},
render: function(createElement) {
return createElement('h1', this.msg)
}
})
</script>

data/computed/watch

1、data

点击展示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<div id="app">
<button @click="addItem">添加</button>
<ul>
<li v-for="(item,index) in list" :key="index">
<a :href="item.url">{{item.name}}</a>
</li>
</ul>
</div>
<script>
var app = new Vue({
el: "#app",
data() {
return {
count: 1,
list: [{
name: 'vue js',
url: 'https://cn.vuejs.org'
}, {
name: 'github',
url: 'https://github.com'
}, {
name: 'blog',
url: 'issummer.cn'
}]
}
},
methods: {
addItem() {
this.count++
this.list.push({
name: 'baidu' + this.count,
url: 'https://www.baidu.com'
})
}
}
})
</script>

2、computed

computed 中的属性是需要先进行计算,然后再返回想要的数据的。当我们输出某个属性,必须依赖另外一个 data 中的属性来动态计算获得的,此时使用 computed 就非常简单了。

点击展示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<div id="app">
<button @click="addItem">添加</button>
<ul>
<li v-for="(item,index) in list" :key="index">
<a :href="item.url">{{item.name}}</a>
</li>
</ul>
</div>
<script>
var app = new Vue({
el: "#app",
data() {
return {
count: 1,
// 这里是后台数据
requestList: [
'vuejs https://cn.vuejs.org',
'github https://github.com',
'blog https://issummer.cn'
]
}
},
computed: {
list: function() {
var list = []
this.requestList.map(function(item, index) {
var tempArr = item.split('-')
list.push({
name: tempArr[0],
url: tempArr[1]
})
})
return list
}
},
methods: {
addItem() {
this.count++
this.requestList.push('blog' + this.count + 'issummer.cn')
}
}
})
</script>

计算是可以修改的,计算属性不仅可以定义为一个函数,也可以定义为一个含有 get/set 属性的对象。当我们定义为一个函数是,Vue 内部会默认将这个函数赋值给 get 属性,一般 set 是未定义的。当我们定义 set 属性后,就可以对它进行修改了。

点击展示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<div id="app">
<button @click="changeName">改变姓名</button>
<h2>{{ username }}</h2>
</div>
<script>
var app = new Vue({
el: "#app",
data() {
return {
firstName: 'Jude',
lastName: 'Summer'
}
},
computed: {
username: {
get: function() {
return this.firstName + ' ' + this.lastName
},
set: function(newVal) {
var names = newVal.split(' ');
this.firstName = names[0];
this.lastName = names[1];
}
}
},
methods:{
changeName(){
if(this.username === 'Jude Summer'){
this.username = "YQ"
}else{
this.username = 'Jude Summer'
}
}
}
})
</script>

3、watch侦听器

创建 Vue 应用时,我们还提到过 watch 这个属性,它其实是个对象,键是需要观察的表达式,值是对应的回调函数。值也可以是方法名,或者包含选项的对象。和上面的计算属性类似,他可以监听 值/表达式 的变化来执行回调函数。

点击展示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
<div id="app">
<button @click="changeName">改变姓名</button>
<h2>{{ username }}</h2>
</div>
<script>
var app = new Vue({
el: "#app",
data() {
return {
firstName: 'Jude',
lastName: 'Summer',
username: 'Jude Summer'
}
},
watch: {
firstName: function (val, oldVal) {
this.username = val + ' ' + this.lastName;
},
lastName: function (val, oldVal) {
this.username = this.firstName + ' ' + val;
}
},
methods: {
changeName() {
if (this.username === 'Jude Summer') {
this.firstName = "Y";
this.lastName = "Q";
} else {
this.firstName = "Jude";
this.lastName = "Summer";
}
}
},
})

</script>
// 监听对象属性
<script>
var app = new Vue({
el: "#app",
data() {
return {
userinfo: {
firstName: 'Jude',
lastName: 'Summer'
},
username: "Jude Summer"
}
},
watch: {
// 此时无论我们如何点击按钮,都无法改变 username 的值,因为 watch 侦听器默认只是侦听该对象本身的赋值操作,也就是直接对 this.userinfo 进行赋值操作时的变化,并未对其内部属性进行侦听
userinfo: function (val, oldVal) {
this.username = val.firstName + ' ' + val.lastName;
}
},
methods: {
changeName: function () {
if (this.username === 'Jude Summer') {
this.userinfo.firstName = 'Y'
this.userinfo.lastName = 'Q'
} else {
this.userinfo.firstName = "Jude"
this.userinfo.lastName = "Summer"
}
}
},
})
</script>
// 上面的方法是无法修改username的值 因为 watch 侦听器默认只是侦听该对象本身的赋值操作,也就是直接对 this.userinfo 进行赋值操作时的变化,并未对其内部属性进行侦听。实际上对于侦听的值是可以为一个对象的,它还有个 deep 属性,用来设置是否侦听内部属性的变化,而回调函数是通过 handler 来设置的
<script>
var app = new Vue({
el: "#app",
data() {
return {
userinfo: {
firstName: 'Jude',
lastName: 'Summer'
},
username: "Jude Summer"
}
},
watch: {
// depp属性用来侦听内部属性变化,回调函数是通过hander来设置
// 适用于非整体赋值 也就是适用于局部修改属性,这个时候通过设置deep属性为true,来达到侦听目的。
userinfo: {
deep: true,
handler: function (val, oldVal) {
this.username = val.firstName + " " + val.lastName;
}
}
},
methods: {
// 修改名字
changeName: function () {
if (this.username === 'Jude Summer') {
this.userinfo.firstName = 'Y'
this.userinfo.lastName = 'Q'
} else {
this.userinfo.firstName = "Jude"
this.userinfo.lastName = "Summer"
}
}
},
})
</script>
// 写成对象属性访问表达式的 当侦听对象包含很多属性,而我们只是需要监听其中的一个或某几个属性,这时如果我们通过这种方式侦听所有内部属性的变化,自然就会造成内存的浪费。那么我们可以只侦听单一内部属性的变化
<script>
var app = new Vue({
el: "#app",
data() {
return {
userinfo: {
firstName: 'Jude',
lastName: 'Summer'
},
username: "Jude Summer"
}
},
watch: {
// 侦听对象的某个属性
'userinfo.lastName':function(val,oldVal){
this.username = this.userinfo.firstName + ' ' + val;
}
},
methods: {
// 修改名字
changeName: function () {
if (this.username === 'Jude Summer') {
// this.userinfo.firstName = 'Y'
this.userinfo.lastName = 'Q'
} else {
// this.userinfo.firstName = "Jude"
this.userinfo.lastName = "Summer"
}
}
},
})
</script>

v-on对象语法

绑定事件监听器。事件类型由参数指定。表达式可以是一个方法的名字或一个内联语句,如果没有修饰符也可以省略。

通常的写法:

1
<button @click="handleClick"></button>

对象语法:

1
2
3
4
<div v-on="{ mouseenter: ShowInfo, mouseleave: HideInfo }">
<div>我是title</div>
<div v-show="ShowWrapper">我是显示的内容!</div>
</div>

axios的封装

router-link

有时候,我们需要将router-link渲染成某种标签,例如<li></li>使用 tag prop 类指定何种标签,同样它还是会监听点击,触发导航

示例:

1
2
3
4
5
<ul class="nav-list">
<router-link tag="li" to="home">home</router-link>
</ul>
<!-- 渲染的结果为 -->
<li>home</li>

v-slot

可放置在函数参数位置的 JavaScript 表达式 (在支持的环境下可使用解构)。可选,即只需要在为插槽传入 prop 的时候使用。提供具名插槽或需要接收 prop 的插槽。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 父组件 -->
<template>
<div class="common-card">
<div class="title">{{ title }}</div>
<div class="value">{{ value }}</div>
<div class="chart">
<slot></slot>
</div>
<div class="line"></div>
<div class="total">
<slot name="footer"></slot>
</div>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
<!-- 子组件 -->
<common-card title="今日交易用户数" :value="1234567890">
<template>
<v-chart :options="getOptions()" />
</template>
<template v-slot:footer>
<span>退货率</span>
<span class="emphasis">12%</span>
</template>
</common-card>

props

props 可以是数组或对象,用于接收来自父组件的数据。props 可以是简单的数组,或者使用对象作为替代,对象允许配置高级选项,如类型检测、自定义验证和设置默认值。

常用于父组件与子组件的通信
对象语法选项包括:

1、type:原生构造函数的一种:String、Number、Boolean、Array、Object、Date、Function、Symbol、任何自定义构造函数。

2、default: any 默认值

3、required:Boolean 该prop是否是必填项

4、validator: Function 校验函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
props: {
// 检测类型
title: String,
// 检测类型 + 其他验证
age: {
type: Number, //类型
default: 0,// 默认值
required: true,// 是否是必填项
// 校验函数
validator: function (value) {
return value >= 0
}
}
}

mixin 混入

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

1、实现原理

将用户传入的对象与 Vue 自身的options属性合并,合并后的对象将会覆盖原来的Vue.options。因为 mixin 方法修改了Vue.options属性,之后创建的每个实例都会用到该属性,所以会影响创建的每个实例。

注意:如果用户传入的对象与组件有相同的数据对象,将会发生合并,并且遵循组件数据优先的原则。对于钩子函数来说,如果相同,将会合并为一个数组,所以都会被调用,只是混入对象的钩子会被先调用。值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

1
2
3
4
5
6
7
8
9
// vue源码 vue/src/core/global-api/mixin.js
import { mergeOptions } from '../util/index'

export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}

2、如何理解mixin?

可以将mixin理解为数组,数组中有单个或者多个mixin,mixin的本质就是js对象,拥有Vue实例的所有属性,例如:data,created,methods等,还可以在mixin中再次嵌套mixin

注意:在组件中引入的方式为 mixins:[myMixin]

3、mixin的实现

当Vue在实例的时候,会调用mergeOptions函数进行options的合并

点击展示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// vue源码 core/util/options.js
export function mergeOptions(
parent: Object,
child: Object,
vm?: Component
): Object {
...
// 如果有 child.extends 递归调用 mergeOptions 实现属性拷贝
const extendsFrom = child.extends
if (extendsFrom) {
parent = mergeOptions(parent, extendsFrom, vm)
}
// 如果有 child.mixins 递归调用 mergeOptions 实现属性拷贝
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
// 申明 options 空对象,用来保存属性拷贝结果
const options = {}
let key
// 遍历 parent 对象,调用 mergeField 进行属性拷贝
for (key in parent) {
mergeField(key)
}
// 遍历 child 对象,调用 mergeField 进行属性拷贝
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
// 属性拷贝实现方法 mergeField 函数接收一个 key,首先会申明 strat 变量,如果 strats[key] 为真,就将 strats[key] 赋值给 strat。
function mergeField(key) {
// 穿透赋值,默认为 defaultStrat
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}

vue 中 mixins 的优先级,component > mixins > extends。

边界处理:inject/provide

类型:

provide:Object | () => Object

inject:Array | { [key: string]: string | Symbol | Object }

provide和inject需要一起使用,可以允许一个祖先组件向其所有子孙后代注入一个依赖,不管组件的层次有多深,并在其上下游关系成立的时间里始终生效。

同react的上下文特性相似

点击展示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 父级组件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}

// 子组件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}

vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。简单的说,vuex就是一个状态管理器。

开发过程,通常会遇到多个视图依赖同一个状态,不同视图的行为需要变更为同一状态(例如:后台管理系统的侧边栏的收缩功能。)

Vuex的核心就是store,它就是一个仓库容器,包含了驱动应用的数据源(state),不同于单纯的全局对象,vuex的状态存储是响应式的,当 Vue组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

不能直接改变 store 中的状态,改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。