Q: 点击按钮的时候动态给 data 增加的成员是否是响应式数据?如果不是的话,如何把新增成员设置成响应式数据,它的内部原理是什么
let vm = new Vue({
el: '#el'
data: {
o: 'object',
dog: {}
},
method: {
clickHandler () {
// 该 name 属性是否是响应式的
this.dog.name = 'Trump'
}
}
})
A:
不是响应式数据
Vue中data成员是在Vue初始化的时候通过 new Observer
将其响应式处理;
新添加的成员,只是在实例上新加了一个属性,没有经过observe处理,所以该成员不是响应式的
方案:来源Vue官方
对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property。还可以使用 vm.$set 实例方法,这也是全局 Vue.set 方法的别名。
文档链接
内部原理
/**
* 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的值是有效的数组索引
// 这块代码意思是在修改数组时调用set方法时让我们能够触发响应的代码
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 类似$vm.set(vm.$data.arr, 0, 3)
// 修改数组的长度, 避免索引>数组长度导致splcie()执行有误
target.length = Math.max(target.length, key)
// 利用数组的splice变异方法触发响应式
target.splice(key, 1, val)
return val
}
// target为对象, key在target或者target.prototype上。
// 并且key不是Object原型上的属性
// 说明这个key本来就在对象上面已经定义过了的,直接修改值就可以了,可以自动触发响应
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
// 以上都不成立, 即开始给target创建一个全新的属性
// vue给响应式对象都加了一个__ob__属性,如果一个对象有这个__ob__属性,
// 那么就说明这个对象是响应式对象,我们修改对象已有属性的时候就会触发页面渲染
// 获取Observer实例
const ob = (target: any).__ob__
// Vue 实例对象拥有 _isVue 属性, 即不允许给 Vue 实例对象添加属性
// 也不允许Vue.set/$set 函数为根数据对象(vm.$data)添加属性
// 即 当前的target对象是vue实例对象或者是根数据对象,那么就会抛出错误警告
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本身就不是响应式数据, 不需要响应,那么直接赋值返回即可
if (!ob) {
target[key] = val
return val
}
// 进行响应式处理
// 给新加的属性添加依赖,以后再直接修改这个新的属性的时候就会触发页面渲染
defineReactive(ob.value, key, val)
// 触发当前的依赖(这里的依赖依然可以理解成渲染函数),所以页面就会进行重新渲染
ob.dep.notify()
return val
}
以上代码列举至源码中的set方法,可以看出响应式处理的部分依赖defineReactive
方法内部实现;
说明一下:
Vue2.X版本中响应式处理主要依赖于Object.defineProperty实现:接触对象中的属性,给属性添加setter/getter
Vue3.X版本中主要依赖于proxy代理对象来监听数据的变化