使用场景:
动态添加一个响应式属性
实例创建之后添加新的属性到实例上,它不会触发视图更新。
由于 JavaScript 的限制,Vue 不能检测以下变动的数组。
通过数组的下标去修改数组的值,数据已经被修改了,但是不触发updated函数,视图不更新,
vue中检测不到对象属性的添加和删除
解决方案:
1).data 对象就像组件状态的模式(schema)。在它上面声明所有的属性让组件代码更易于理解。
2).添加一个顶级响应属性会强制所有的 watcher 重新计算,因为它之前不存在,没有 watcher 追踪它。这么做性能通常是可以接受的(特别是对比 Angular 的脏 检 查,但是可以在初始化时避免。
2.Vue.set,vm.$set() ,方法调用时,页面会全部更新一遍。
3.数组的splice, concat 方法
<div id="app2">
<p v-for="item in items" :key="item.id">
{{item.message}}
</p>
<button class="btn" @click="btn2Click()">动态赋值</button><br/>
<button class="btn" @click="btn3Click()">为data新增属性</button>
</div>
<script src="../../dist/vue.min.js"></script>
<script>
var vm2=new Vue({
el:"#app2",
data:{
items:[
{message:"Test one",id:"1"},
{message:"Test two",id:"2"},
{message:"Test three",id:"3"}
]
},
methods:{
btn2Click:function(){
Vue.set(this.items,0,{message:"Change Test",id:'10'})
},
btn3Click:function(){
var itemLen=this.items.length;
Vue.set(this.items,itemLen,{message:"Test add attr",id:itemLen});
}
}
});
</script>
set源码解析
定义位置 global-api/index.js
Vue.set()
// 静态方法 set/delete/nextTick
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
vm.$set()
instance/index.js
// 注册vm 的 $data/ $props/ $set / $delete / $watch
// instance/state.js
stateMixin(Vue)
// instance/state.js
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.set 与 vm.$set() 是相同函数
/**
* Set a property on an object. Adds the new property and
* triggers change notification if the property doesn't
* already exist.
*/
export function set (target: Array<any> | Object, key: any, val: any): any {
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)) {
target.length = Math.max(target.length, key)
// 通过 splice 对 key位置的元素进行替换
// splice 在 array.js 进行了响应化处理
target.splice(key, 1, val)
return val
}
// 如果 key 在对象中以及存在直接赋值
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
// 获取 target 中的 observer 对象
const ob = (target: any).__ob__
// 如果target 是vue 实例或者$data 直接返回
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
}
执行过程
- set内部先做辅助判断
- 判断target是否是对象,key是否是合格的索引
- 求数组的长度,调用修改过后的数组方法
- 进入处理数组方法中,修改为响应式数据,调用dep.depend()方法更新
- 视图更新