问题
export default {
data() {
return {
obj: {
a: 1,
b: 2
}
}
}
}
- 当我们动态向obj对象中添加属性时,
this.obj.c = 3; - 当我们动态删除obj对象中的一个属性时,
delete this.obj.b
上述两种情况,数据是否还是响应式的?页面是否还会更新?通过调试我们发现,这样页面没有更新。在Vue初始化实例的时候,会遍历data中的属性,如果是对象的话,那就遍历对象中的每个属性,为每个数据添加响应式处理,所以现在动态的添加属性或删除属性,vue并没有做处理,所以不会触发派发更新的操作。
解决方案
vue提供了set和delete方法,来处理对象动态添加和删除属性的操作。
Vue.set官方解释
-
参数:
{Object | Array} target{string | number} propertyName/index{any} value
-
返回值:设置的值。
-
用法:
向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如
this.myObject.newProperty = 'hi')
Vue.delete官方解释
-
参数:
-
{Object | Array} target -
{string | number} propertyName/index
仅在 2.2.0+ 版本中支持 Array + index 用法。
-
-
用法:
删除对象的 property。如果对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开 Vue 不能检测到 property 被删除的限制,但是你应该很少会使用它。
所以我们可以用set和delete方法来解决上述的问题
源码
- 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
}
首先判断参数不能是undefined、null以及基本数据类型,如果是数组的话会调用修改后的splice方法,让新增的值变成响应式数据,如果是对象的话则调用defineReactive方法将属性变成响应式数据,最后调用notify方法,来通知watcher来更新数据。还可以看到如果直接修改根节点的$data或者修改一个vue实例的话,直接会返回,并给出警告,这也是官方文档上写的目标对象不能是一个 Vue 实例或 Vue 实例的根数据对象。
- delete源码
export function del (target: Array<any> | Object, key: any) {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.'
)
return
}
if (!hasOwn(target, key)) {
return
}
delete target[key]
if (!ob) {
return
}
ob.dep.notify()
}
Vue.delete实现原理和set类似,只不过是删除属性,在删除完属性之后调用notify方法来通知watcher进行更新数据。