问题描述
在使用Vue日常工作开发中,偶尔会遇到这种问题,明明我已经修改了数据,但是视图却没有更新。比如下面这些骚操作:
直接在对象上新增属性
直接删除对象属性
通过角标[]直接修改数组的某一项
直接修改数组的length
问题分析
想要了解为什么上面这些写法不会触发视图更新,只需要搞清楚在vue中是如何对数据进行响应式处理的。知道了vue的数据响应机制,那么跳出机制的写法自然就不能触发视图更新了。
在vue中,对于对象和数组会进行不同的响应式处理
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
对象
如果是对象,会在组件初始化的时候通过Object.defineProperty对每个属性进行响应式处理,但是这个过程是一次性的,说白了就是过了这个村就没这个店了,没上车的就只能吸着尾气目送队友离开
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
我们后来手动新增和删除的属性并不是响应式的,也就不会触发视图更新。
数组
如果是数组,则会修改数据的__proto__属性
function protoAugment (target, src: Object) {
target.__proto__ = src
}
这里的src根据上面的代码看到传入的是arrayMethods, 这个arrayMethods定义如下:
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
然后对七个能改变数组的方法进行拦截,当我们调用这七个方法修改数组的时候,会先执行原始方法,然后dep通知watcher更新依赖
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
// 关键点在这,通知依赖更新
ob.dep.notify()
return result
})
})
我们使用角标修改数组和更改数组的length都不在上面的机制之内,所以数据改变视图也不会更新
如何解决
两种方法
调用[vm.\$forceUpdate](https://cn.vuejs.org/v2/api/#vm-forceUpdate)方法强制重新渲染组件
如果你一定要用上面那些写法,那么可以调用这个方法强行使组件重新渲染,保证展示最新修改的数据
使用[Vue.set](https://cn.vuejs.org/v2/api/#Vue-set)和[Vue.delete](https://cn.vuejs.org/v2/api/#Vue-delete)为数据新增或删除属性/子项(调用vm.\$set和vm.\$delete原理相同)
Vue.set = set
Vue.delete = del
export function set (target: Array | Object, key: any, val: any): any {
// 此处省略一万行代码。。。
// 为新增的属性定义响应式
defineReactive(ob.value, key, val)
// 通知依赖更新
ob.dep.notify()
return val
}
export function del (target: Array | Object, key: any) {
// 此处省略一万行代码。。。
if (!hasOwn(target, key)) {
return
}
delete target[key]
if (!ob) {
return
}
// 通知依赖更新
ob.dep.notify()
}
这两个方法都很好理解,核心就是dep通知watcher更新外界依赖
结论
出现视图不更新的情况,基本都是做了一些跳出了Vue数据响应机制之外的骚操作,大家在工作开发中还是应该避免使用这些写法。另外,官方也不推荐使用Vue.set和Vue.delete方法,提供这两个方法也只是无奈之举,对于数据我们还是统一维护比较好。
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
作者: zencode
原文链接:https://juejin.im/post/6844904169275392013