动态给vue的data添加新属性页面不更新的原因分析以及解决方法
直接添加属性的问题
我们从一个例子触发
定义一个p标签,通过v-for指令进行遍历,然后通过绑定事件,触发事件的时候,将动态添加哟个属性。
预期结果:动态增加的属性也被遍历显示在页面上
{{ value }}
动态添加新属性
实力化一个vue实力,定义data属性喝methods方法
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实现数据响应式的
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
obj.foo obj.foo = 'new'
但是我们为obj添加新的属性的时候,却无法触发事件的拦截
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
function set (target: Array | 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.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实现数据响应式的,直接动态添加新属性仍可以实现数据响应式
还没有评论,来说两句吧...