使用vue,遇到几次修改了对象的属性后,页面并不重新渲染
一、直接添加属性的问题
<template>
<div>
<p v-for="(value,key) in item" :key="key">
{{ value }}
</p>
<button @click="addProperty">动态添加新属性</button>
</div>
</template>
<script>
export default {
data(){
return{
item:{
oldProperty:"旧值"
}
}
},
methods:{
addProperty(){
this.item.newProperty = "新值" // 为items添加新属性
console.log(this.item) // 输出带有newProperty的items
}
}
};
</script>
点击按钮,发现结果不及预期,数据虽然更新了(console
打印出了新属性),但页面并没有更新
二、原理分析
下面来分析一下
vue2
是用过Object.defineProperty
实现数据响应式
const item = {}
Object.defineProperty(obj, 'oldProperty', {
get() {
console.log(`get oldProperty:${val}`);
return val
},
set(newVal) {
if (newVal !== val) {
console.log(`set oldProperty:${newVal}`);
val = newVal
}
}
})
}
原因:组件初始化时,对data
中的item
进行递归遍历,对item的每一个属性进行劫持,添加set,get
方法。我们后来新加的newProperty
属性,并没有通过Object.defineProperty
设置成响应式数据,修改后不会视图更新。
三、解决方案
Vue
不允许在已经创建的实例上动态添加新的响应式属性
若想实现数据与视图同步更新,可采取下面四种解决方案:
- Vue.set()
- Object.assign()
- $forcecUpdated()
- … 展开语法
- 在组件上进行 key 更改
Vue.set()
Vue.set( target, propertyName/index, value )
参数:
target: 要更改的数据源(可以是一个对象或者数组)
key 要更改的具体数据。如果是数组元素更改,key表示索引;如果是对象,key表示键值
value 重新赋的值
this.$set(this.item, "newProperty", "新值");
this.$set(this.persons, 1, {key: 'newkey', name: '888'})
vue源码:
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
实现属性拦截
大致代码如下:
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()
添加到对象的新属性不会触发更新
应创建一个新的对象,合并原对象和混入对象的属性
this.item = Object.assign({},this.item,{newProperty:'新值'})
$forceUpdate
如果你发现你自己需要在 Vue中做一次强制更新,99.9%
的情况,是你在某个地方做错了事
$forceUpdate
迫使Vue 实例重新渲染
PS:仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
this.item.newProperty = "新值"
this.$forceUpdate();
… 展开语法
对象数据obj
,使用obj = {...obj}
对于数组arr
,使用arr = [...arr]
add() {
this.persons[1] = {key: 'newkey', name: '888'}
this.persons = [...this.persons]
console.log(this.persons)
}
在组件上进行 key 更改
<template>
<div :key="componentKey">
<p v-for="(value,key) in item" :key="key">
{{ value }}
</p>
<button @click="addProperty">动态添加新属性</button>
</div>
</template>
<script>
export default {
data(){
return{
item:{
oldProperty:"旧值"
},
componentKey:0
}
},
methods:{
addProperty(){
this.item.newProperty = "新值" // 为items添加新属性
console.log(this.item) // 输出带有newProperty的items
this.componentKey += 1;
}
}
};
</script>