当你使用Vue的时候,可能会遇到一种情况:当向一个已经定义好的Vue实例中给不存在的属性赋值时,这个属性不会自动响应式地更新视图。这是因为Vue在实例化时只对已经存在的属性设置了响应式,如果后续添加了新的属性,就需要手动调用$set去设置响应式。
$set实现的原理是通过调用对象的defineReactive()方法,将新增的属性动态转换成getter/setter,并将其添加到Vue实例的响应式数据中。定义响应式数据的过程需要从Vue源码中逐步分析。
首先在Vue.js中使用了defineProperty()方法来实现数据双向绑定的。在new Vue()初始化创建实例时,会调用initState()方法为实例添加响应式数据,最终走到set()方法,执行赋值操作时会调用defineReactive()方法进行Setter的监听。
defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
return val
},
set: function reactiveSetter(newVal) {
if (newVal === val) {
return
}
val = newVal
// 触发更新
dep.notify()
}
})
}
而$set()实现的关键在于调用了Vue源码核心所在的defineProperty()方法,并将属性设置为响应式:
Vue.set(obj, 'newkey', 123)
Vue源码如下--
function set (target: Array
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
}
举个实例:
let vm = new Vue({
data: {
person: {
name: 'Tom'
}
}
})
vm.$set(vm.person, 'age', 20)
第一步,将vm.person传给$set(),这个person对象包含属性name。
第二步,调用Vue.set(obj, 'newkey', 123)为person对象动态添加age属性。Vue源码中判断age属性本来不存在,就会走到新添加属性的逻辑中,调用defineReactive()方法对其实现双向绑定。
第三步,这时候Vue已经将age属性转换成了getter/setter,并添加到了响应式数据中。这意味着,当不通过$set()而是直接通过person.age = 18修改age属性时,并不会触发视图更新。
所以说,当我们使用$set()方法时,$set()实际上是将数据变为响应式,即使数据是在定义实例后动态添加的。
再举一个实例:
let vmData = {
person: {
name: 'Tom'
}
}
// 将vmData变成Vue实例的数据,使其响应式
let vm = new Vue({
data: vmData
})
// 这次不用$set,直接定义属性
vmData.person.age = 20
console.log(vmData.person.age) // 20
这里我们将vmData定义为一个普通的对象,并且在实例化Vue时将它作为响应式数据传入。接着,直接给person.age赋值,这样控制台输出的结果就是20。
这时你会发现,定义一个普通对象并将它作为响应式数据传入,这个对象的属性也会随着Vue实例的改变而改变,这是因为Vue在实例化时对所有属性都是自动进行了响应式的处理。
总之,$set() 就是为了解决Vue在实例化时无法自动新增属性并响应的问题。通过使用$set(),可以将新增的属性变成响应式的,从而触发组件的重新渲染视图。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:你知道Vue中神奇的$set是如何实现的吗? - Python技术站