本文已参与「新人创作礼」活动,一起开启掘金创作之路。
响应式数据中存在的问题
关于Vue2.x数据响应式有一些值得注意的点,这些点在日常的开发过程中,很可能会遇到,了解原理有助于我们对这些情况的处理。
1.关于响应式对象
如下代码,当我们在Vue组件中动态新增对象obj上的属性msg2,这样的情况下定义的数据是非响应式的,只有在data中初始化的时候定义的数据(如msg1)才是响应式的。这是为什么呢?在之前的文章中,我们分析过数据是如何被响应式的,在initData的过程中,定义在data中的对象会被依次遍历,执行observe方法,当对象是个深层次的对象的时候,会递归执行observe方法,直到对象上所有的属性被访问到。而未在data中定义的属性(比如下面例子中的msg2)在数据的init的过程中,就无法访问到来做响应式的处理。
export default {
data() {
return {
obj: {
// msg1是响应式数据
msg1: 'hello world',
}
}
}
created() {
// 这样定义的msg2是非响应式数据
this.obj.msg2 = 'hello Vue'
}
}
那在实际开发过程中,我们需要给对象动态添加响应式属性,该怎么做呢,Vue的官方提供了一个API
Vue.set( target, propertyName/index, value )
我们看下这个API的源码:
/**
* 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是个有效的index,直接通过调用splice方法去添加进数组然后返回这个值,这个splice方法已经改写过了
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
// target 是普通对象的情况下,执行下面的逻辑
// key 已经存在于对象上且非原型链上查找的值,直接将对象上的值修改为传入的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
}
// target.__ob__不存在的话,说明不是响应式的对象,直接返回
if (!ob) {
target[key] = val
return val
}
// 以上条件都满足,调用defineReactive,对新属性进行响应式处理
defineReactive(ob.value, key, val)
// 通知与ob相关的依赖做更新
ob.dep.notify()
return val
}
2.关于响应式数组
关于数组,Vue 也是不能检测到以下动态变化的数组:
// 通过下标修改值
vm.arr[index] = newValue
// 修改数组长度
vm.arr.length = newLength
对于通过下标修改值的情况,可以使用Vue.set方法:
Vue.set(arr, index, newValue)
// target是数组的话,且key是个有效的index,直接通过调用splice方法去添加进数组然后返回这个值
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
这里的splice已经被重写过了,不清楚这段逻辑可以点击这里,splice会遍历数组中的值,做响应式处理,并通知数组的依赖做更新。
关于修改数组长度,可以使用splice方法:
arr.splice(newLength)