目录
Vue.set()使用
Vue.delete()的使用
Vue.set()方法原理解析
总结
Vue.set()使用
vue 在实例上添加新的属性的时候,该属性,并不是响应式的。同样删除某一属性的时候,也不会实时渲染到页面上。
比如:
<p> 年龄:{{obj.age? obj.age: "无"}}</p>
···········
data() {
return {
obj:{ name:"Lena", id:1 },
}
}
页面上 显示的是 年龄:无 现在需要添加一个响应式的属性 age 。
<template>
<div class="app">
<ul>
<li> 年龄:{{obj.age? obj.age: "无"}}</li>
</ul>
<button @click="add()">添加年龄属性</button>
<button @click="show()">打印</button>
</div>
</template>
<script>
import Vue from 'vue'
export default {
component:{},
data() {
return {
obj:{ name:"Lena", id:1 },
}
},
methods: {
add(){
this.obj.age= 20
},
show(){
console.log('obj',this.obj)
}
}
}
</script>
效果:
通过 this.obj.age= 20 ,控制台打印已经有了该属性,并没有渲染到页面上。 可见,这种方式添加的属性 age 并不是响应式的。
使用Vue.set() ,更改add()方法:
add(){
Vue.set(this.obj,'age', '20')
},
效果:
因为vue不能检测到对象属性的添加或者删除,只有在data对象上存在的属性是响应式的,所以要使用Vue.set()方法将响应式属性添加到对象上。同样的道理,删除对象 Vue.delete也是如此。
Vue.delete()的使用
<template>
<div class="app">
<ul>
<li> 年龄:{{obj.age? obj.age: "无"}}</li>
</ul>
<button @click="add()">添加年龄属性</button>
<button @click="del()">删除年龄属性</button>
<button @click="show()">打印</button>
</div>
</template>
<script>
import Vue from 'vue'
export default {
component:{
},
data() {
return {
obj:{ name:"Lena", id:1 },
}
},
methods: {
add(){
Vue.set(this.obj,'age', '20')
},
del(){
Vue.delete(this.obj,'age')
},
show(){
console.log('obj',this.obj)
}
}
}
</script>
效果:
del() 方法如果是下面两种,同样不是响应式的。
del(){
delete this.obj.age
},
或者:
del(){
this.obj = { name:"Lena", id:1 }
},
Vue.set()方法原理解析
我们找到封装set方法的地方:.........\node_modules\vue\src\core\observer\index.js
找到封装的set方法:
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)}`)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
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
}
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
对象调用:Vue.set( target ,'age', 20 )
数组调用:Vue.set( array , 0, 20 ) //数组对象,索引,值
首先是判断是否是开发环境并且 对象是否被定义isUndef(target)或者是否是基础类型isPrimitive(target),否则会报错:
`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
)
如果是数组的话,调用重写的splice()方法,可以更新视图。
isValidArrayIndex(key)方法用来验证是否是一个有效的数组索引, 其实就是验证是否是一个非无穷大的正整数。
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
如果对象本身就有所要添加的属性,那只需要直接赋值就可以。
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
如果是Vue实例,或者是根数据data的时候,就会报错。
如果本身就不是响应式的,只需要直接赋值即可。
const ob = (target: any).__ob__
//如果是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
}
//如果本身就不是响应式的,只需要直接赋值即可。
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
排除各种不合适的,最后给当前对象定义一个属性:defineReactive(ob.value, key, val) 相当于用了 Object.defineProperty 重新定义了一下。
最后,手动通知视图更新:ob.dep.notify()
总结
这个 set方法,对于数组来说,调用的就是splice,对于对象来说,使用的就是defineReactive,再添加了一个手动的视图更新。这就是set的原理。