目录

1、Vue 的数据不是响应式吗?

2、如何解决上述的问题

3、一些自己的见解


今天做项目的过程中遇到一个问题,百思不得其解,询问大佬后恍然大悟,踩坑的主要原因是知识盲区,上网搜到一篇比较好的简书博客,转载过来,并加上自己的一些见解,希望能帮到大家,原文链接: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 官方文档关于数组变异方法的说明:传送门

Logo

前往低代码交流专区

更多推荐