淘先锋技术网

首页 1 2 3 4 5 6 7

问题描述

在使用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])

}

}

5b27138509161544710e0795c13ecd58.png

我们后来手动新增和删除的属性并不是响应式的,也就不会触发视图更新。

数组

如果是数组,则会修改数据的__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

})

})

e08243001c3ee6f01c0b97c5d39a72dd.png

我们使用角标修改数组和更改数组的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