Appearance
五、$set
动态添加一个响应式属性$set
准备html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Vue.js 01 component example</title>
</head>
<body>
<div id="app">
{{ obj.title }}
<hr>
{{ obj.name }}
<hr>
{{ arr }}
</div>
<script src="../../dist/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
obj: {
title: 'Hello Vue!'
},
arr: [1, 2, 3]
}
})
</script>
</body>
</html>
控制台设置值
如果要给obj.name赋值,是无法更新视图的
一般用 Vue.set 或者 vm.$set 方法,常用的是vm.$set,因为我们在项目中不太容易获取Vue的构造函数.
vm.$set(vm.obj, 'name', 'xm)
这个时候视图上出现了xm,说明添加的这个name是响应式数据.
如果修改数组的元素,之前使用splice方法可以,使用$set也可以
js
// 将第一个元素设置为100
vm.$set(vm.arr, 0, 100)
vm.$set
- 是Vue.set的别名.
- 添加一个响应式的属性
- 必须要在响应式对象上添加
- 不能是Vue实例或者Vue的跟数据对象
源码解析
- Vue.set()
- global-api/index.js
- 里面的set方法是在observer/index.js中定义(与响应式相关)
- vm.$set()
- instance/index.js
- 里面的stateMixin函数在instance/state.js
- 里面的Vue.prototype.$set也是在observer/index.js中定义,与静态方法set是一个方法
js
export function set (target: Array<any> | Object, key: any, val: any): any {
// 判断传入的目标对象是否是undefined或者是原始值,不允许给undefined或者原始值添加响应式属性,会发送警告
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
// 判断target对象是否是数组,并且key是合法的索引
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 比较key和数组的length属性谁大,然后赋值给length属性,调用$set的时候数组可能会超过length属性
target.length = Math.max(target.length, key)
// 调用splice方法进行替换,这个不是数组的原生方法,是之前修改过的splice方法
target.splice(key, 1, val)
return val
}
// 处理对象属性
// 判断处理的属性在对象中已经存在,并且这个属性不是Object原型上的成员,就直接赋值,不需要进行响应式处理
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
// 获取target的__ob__属性,判断其是不是vm或者是$data,是的话抛出警告
// $data的话ob.vmCount是1
const ob = (target: any).__ob__
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不是响应式对象,如果target不是响应式对象,那么传入的属性也不必做响应式处理,直接赋值返回
if (!ob) {
target[key] = val
return val
}
// 如果ob存在,就把属性设置成响应式属性,ob.value即target
defineReactive(ob.value, key, val)
// 还要发送通知
// 可以这么做是我们在收集依赖的时候,给每一个子对象都创建了childObj,并且给childObj的dep也收集了依赖
// 因为那个收集了依赖,所以这里可以发送通知
ob.dep.notify()
// 最后将值返回
return val
}