直接添加属性的问题
我们从一个例子触发
定义一个p标签,通过v-for指令进行遍历,然后通过绑定事件,触发事件的时候,将动态添加哟个属性。
预期结果:动态增加的属性也被遍历显示在页面上
html
<p v-for="(value,key) in item" :key="key">
{{ value }}
</p>
<button @click="addProperty">动态添加新属性</button>
实力化一个vue实力,定义data属性喝methods方法
javascript
const app = new Vue({
el:"#app",
data:()=>{
item:{
oldProperty:"旧属性"
}
},
methods:{
addProperty(){
this.items.newProperty = "新属性" // 为items添加新属性
console.log(this.items) // 输出带有newProperty的items
}
}
})
点击按钮,发现结果不及预期,数据虽然也更新了(console打印出了新属性),但是页面并没有更新。
原因分析
我们都知道,在vue2中,是使用Obejct.defineProperty实现数据响应式的
javascript
const obj = {}
Object.defineProperty(obj, 'foo', {
get() {
console.log(`get foo:${val}`);
return val
},
set(newVal) {
if (newVal !== val) {
console.log(`set foo:${newVal}`);
val = newVal
}
}
})
}
当我们访问或者设置对象已经存在的属性,例如上面的foo,都能够触发setter与getter
javascript
obj.foo
obj.foo = 'new'
但是我们为obj添加新的属性的时候,却无法触发事件的拦截
javascript
obj.bar = '新属性'
原因是一开始obj的foo属性被设置成了响应式数据,而bar是最后新增的属性,并没有通过Object.defineProperty设置成响应式数据
解决方案
vue不允许在已经创建的实例上动态添加新的响应式属性,若想实现数据喝视图同步更新,可采取下面三种解决方案:
- Vue.$set()
- $forceUpdated()
- Object.assign()
Vue.set
Vue.set( target, propertyName/index, value )
参数
{Object | Array} target
{string | number} propertyName/index
{any} value
返回值:设置的值
通过Vue.set向响应式对象中添加一个property,并确保这个新property同样是响应式的,且触发视图更新
关于Vue.set源码(省略了很多与本次讨论不相关的代码)
源码位置:src\core\observer\index.js
javascript
function set (target: Array<any> | Object, key: any, val: any): any {
...
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
这里无非再次调用defineReactive方法,实现新增属性的响应式
关于defineReactive方法,内部还是通过Object.defineProperty实现属性拦截,大致如下:
javascript
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`get ${key}:${val}`);
return val
},
set(newVal) {
if (newVal !== val) {
console.log(`set ${key}:${newVal}`);
val = newVal
}
}
})
}
Object.assign()
直接使用Object.assign()添加到对象的新属性不会触发更新,应该创建一个新的对象,合并原对象和混入对象的属性
javascript
this.someObject = Object.assign({},this.someObject,{newProperty1:1,newProperty2:2 ...})
$forceUpdate
如果你发现你自己需要在Vue中做一次强制更新,99.9%的情况,是你在某个地方做出了事(狗头.jpg)
$forceUpdate
迫使Vue
实例重新渲染
PS:仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
小结
- 如果为对象添加少量的新属性,可以直接采用Vue.set()
- 如果需要为对象添加大量的新属性,则采用Object.assign()创建新对象
- 如果实在不知道怎么操作,可以采用$forceUpdate()进行强制刷新(不建议)
PS:vue3是用proxy实现数据响应式的,直接动态添加新属性仍可以实现数据响应式