Vue.set()和this.$set()应用的场景

在 Vue 2.X 项目开发中,有时候需要对数组进行修改,或是对对象新增一个属性,但是发现页面并不会同步更新。例如:

1
2
3
4
5
6
7
8
9
10
const vm = new Vue({
data: {
arr: [1, 2],
obj: {
a: 3,
},
},
});
vm.$data.arr[0] = 3; // 页面不会发生改变
vm.$data.obj.b = 3; // 页面不会发生改变

此时就需要使用到 Vue.set() 或 this.$set()。这个2个的使用方法一样,不过一个是挂载在Vue身上,一个挂载在Vue.prototype上

1
2
3
4
5
6
// Vue.set
import { set } from "../observer/index";
Vue.set = set;
// this.$set
import { set } from "../observer/index";
Vue.prototype.$set = se;

使用Vue.set或this.$set来实现页面不更新的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const vm = new Vue({
data: {
arr: [1, 2],
obj: {
a: 3,
},
},
});
// 修改数组
Vue.set(vm.$data.arr, 0, 3);
vm.$set(vm.$data.arr, 0, 3);
// 对象新增属性
Vue.set(vm.$data.obj, "b", 3);
vm.$set(vm.$data.obj, "b", 3);

set函数的源代码

../observer/index

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
function set (target: Array<any> | Object, key: any, val: any): any {
// isUndef(target):用于判断传入的target是否是undefined或null
// isPrimitive(target):用于判断传入的target是否是原始数据类型
// 如果是 null 或者是 undefined 时,抛出异常
if (process.env.NODE_ENV !== 'production' &&(isUndef(target) || isPrimitive(target))) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}

// 如果传入了一个数组,并且传入的key是数组的有效值
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 用于设置数组的最大length
// Math.max(target.length, key):取数组的length和key二者中的最大值
target.length = Math.max(target.length, key)
// 直接调用数组身上的splice方法,因为Vue里变异了数组方法,调用会触发dep.notify()
target.splice(key, 1, val)
return val
}

// 接下来就是对象的判断了
// 如果传入的 key 在原对象中,说明已经是响应式了,直接修改即可
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}

// 这里到最后的代码,都是对象不在原对象上,是新增的属性值
const ob = (target: any).__ob__
// 如果是Vue实例对象,或根数据对象,则抛出警告
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production'
&&
warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}

// 如果没有 __ob__ 表示当前 target 不是响应式的,那么直接赋值
if (!ob) {
target[key] = val
return val
}

// 剩下就是给响应式对象增加一个 key 新属性,并通知更新
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}