Vue - 给对象新增属性(使用Vue.$set())
今天做项目的过程中遇到一个问题,百思不得其解,询问大佬后恍然大悟,踩坑的主要原因是知识盲区,上网搜到一篇比较好的简书博客,转载过来,希望能帮到大家,原文链接:Vue - 给对象新增属性(使用Vue.$set())。在开发过程中,我们时常会遇到这样一种情况:当 vue 的 data 里边声明或者已经赋值过的对象或者数组(数组里边的值是对象)时,向对象中添加新的属性,如果更新此属性的值,是不会更新..
目录
今天做项目的过程中遇到一个问题,百思不得其解,询问大佬后恍然大悟,踩坑的主要原因是知识盲区,上网搜到一篇比较好的简书博客,转载过来,并加上自己的一些见解,希望能帮到大家,原文链接:Vue - 给对象新增属性(使用Vue.$set())。
1、Vue 的数据不是响应式吗?
在开发过程中,我们可能会遇到这样一种情况:当 vue 的 data 里边声明或者已经赋值过的对象或者数组(数组里边的值是对象)时,向对象中添加新的属性,如果更新此属性的值,是不会更新视图的。
根据官方文档定义:如果在实例创建之后添加新的属性到实例上,它不会触发视图更新。
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty() 把这些属性全部转为 getter/setter,从而实现对 data 的监听,从而实现一旦 data 改变就刷新视图的效果。
受现代 JavaScript 的限制 (以及废弃 Object.observe),Vue 不能检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。
看以下实例:
<template>
<div>
<p @click="addd(obj)">{{obj.d}}</p>
<p @click="adde(obj)"> {{obj.e}}</p>
</div>
</template>
<script>
export default {
data(){
return {
obj:{}
}
},
mounted() {
this.obj = {d: 0};
this.obj.e = 0;
console.log('after--', this.obj);
},
methods: {
addd(item) {
item.d = item.d + 1;
console.log('item--',item);
},
adde(item) {
item.e = item.e + 1;
console.log('item--',item);
}
}
}
可以看出 d 属性是有 get 和 set 方法的,而新增的 e 属性是没有的,这个 e 就是 data 新增的属性,Vue 是不会监听它的。
点击触发 3 次 addd 事件,点击触发 3 次 adde 事件,页面效果及控制台信息如下
此时再触发 1 次 addd 事件,页面效果如下:
由此可以看出,更新新增属性 e,是不会更新视图,但是会改变其值,当更新原有属性 d 时,会更新视图,同时将新增的属性 e 的值也更新到视图里边。
2、如何解决上述的问题
官方定义:Vue 不允许在已经创建的实例上动态添加新的根级响应式属性 (root-level reactive property)。然而它可以使用 Vue.set(object, key, value) 或 this.$set(object, key, value) 方法将响应属性添加到嵌套的对象上:
Vue.set(vm.obj, 'e', 0); //你还可以使用 vm.$set 实例方法,这也是全局 Vue.set 方法的别名:
this.$set(this.obj,'e',02);
有时你想向已有对象上添加一些属性,例如使用 Object.assign() 或 _.extend() 方法来添加属性。但是,添加到对象上的新属性不会触发更新。在这种情况下可以创建一个新的对象,让它包含原对象的属性和新的属性:
// 代替 Object.assign(this.obj, { a: 1, e: 2 })
this.obj = Object.assign({}, this.obj, { a: 1, e: 2 })
上述实例解决如下:
点击触发 3 次 addd 事件,点击触发 3 次 adde事件,页面效果及控制台信息如下:
3、一些自己的见解
博客一开始讲到了,实例创建之后添加新的属性到实例上,它不会触发视图更新。之所以会这样,要涉及到 Vue 的数据响应式原理,Vue 通过 Object.defineProperty( vm,'args',{ value:default } );实现对数据 args 的监听,一旦 vm 监听到 args 数据的改变就会刷新视图,但是如果是后来追加的属性,则是不会调用 Object.defineProperty() 对新属性进行监听的,如果新属性改变了,虽然 args 在内存中的值会改变,但是 vm 实例是监听不到 args 的,所以不会调用 render() 函数对视图进行刷新。
如果想让视图刷新,可以通过修改 vm 上的已经被监听的数据,数据改变后就会被 vm 监听到,然后调用 render() 函数进行视图刷新,这样就会顺带将未被监听的属性一并渲染到页面上,当然这种方法是不推荐的,这也就是我踩得坑,控制台能打印出未被监听数据的改变,但是页面没刷新,是显示的原来的值,差点没坑死我!
Vue 通过 Object.defineProperty() 的方式实现对数据的监听和响应式,导致的后来新增的属性不会被监听到!
针对上述问题,Vue 官方推荐的 Vue.set(object, key, value) 或 this.$set(object, key, value) 的方式( Vue.set()、vm.$set()、this.$set() 三者其实是一个东西),能够实现对新增属性的监听,那该方法的底层到底做了什么呢?
- 新增 key 属性,但是这个属性并没有被 Vue 监听。
- Vue 对新增的属性自动创建代理和监听(如果没有创建过)。
- 新增属性创建后,会触发 Vue 进行视图更新(但并不会立刻更新)。
避免这个坑的要领有两中方式,第一种:在 Vue 实例创建之前就将所有的属性声明好(即使没有默认值,但是后续操作会用到该属性)。第二种:在实例创建后,如果需要追加新属性可以通过 Vue 官方提供的 Vue.set() 方法,保证新属性的数据响应式。
注意:如果实例中的数据是数组的话,因为数组中的元素及长度可能不确定,没有办法在实例创建前完全声明好,所以只能用 Vue.set() 的方式来对数组中相关的新属性进行操作。理论上是这样(this.$set 作用于数组时,并不会自动添加监听和代理),但是 Vue 针对这一问题将数组数据进行看加工,可以实现对数组的直接改变,无需进行上述两种避坑操作就可实现数组数据的响应式:就是在数组的原型之前加一层原型,这层原型涵盖了几个 变异 后的数组 API,具体有:pop()、push()、shift()、unshift()、sort()、splice()、reverse() 七个方法,这几个方法数组原型上的方法同名,但是它内部会有 Vue.set() 的操作,这样开发人员在操作数组数据时,就不需要担心新增的数组内属性不会被视图所监听了。
附上 Vue 官方文档关于数组变异方法的说明:传送门
更多推荐
所有评论(0)