动态给vue的data添加一个新的属性时会发生什么?怎样解决?

Vue2通过Object.defineProperty实现数据响应式,当添加新属性时不会触发setter/getter。解决方法包括使用Vue.set()、Object.assign()和$forceUpdate(),其中Vue.set()能确保新属性响应式,$forceUpdate()用于强制更新视图。
摘要由CSDN通过智能技术生成

原理

vue2是用过Object.defineProperty实现数据响应式

当你把一个普通的JS对象传给vue实例的data选项时,vue将遍历此对象的所有属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter 。Object.defineProperty是ES5中一个无法shim的特性,这也就是为什么vue不支持IE8以及更低版本的浏览器。

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属性或者设置foo值的时候都能够触发setter与getter

obj.foo   
obj.foo ='new'

但是我们为obj添加新属性的时候,却无法触发事件属性的拦截

obj.bar  ='新属性'

原因是一开始obj的foo属性被设成了响应式数据,而bar是后面新增的属性,并没有通过Object.defineProperty设置成响应式数据

因为受现代JS的限制,vue不能检测到对象属性的添加或删除。

由于vue会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在data对象上存在才能让vue转换它,这样它才能是响应的。

vue不允许在已经创建的实例上动态添加新的根级响应式属性

解决方案

Vue 不允许在已经创建的实例上动态添加新的响应式属性

若想实现数据与视图同步更新,可采取下面三种解决方案:

  • Vue.set()。this.$set

  • Object.assign()

  • $forcecUpdated()

#Vue.set()。this.$set

Vue.set()。this.$set的区别

Vue.set()和this.$set()这两个api的实现原理基本一模一样,都是使用了set函数。set函数是从 ../observer/index 文件中导出的,区别在于Vue.set()是将set函数绑定在Vue构造函数上,this.$set()是将set函数绑定在Vue原型上。

使用格式

Vue.set( target, propertyName/index, value )

参数

  • {Object | Array}你要添加到那个对象: target

  • {string | number} 属性或下标:propertyName/index

  • {any}值: value

返回值:设置的值

通过Vue.set向响应式对象中添加一个property,并确保这个新 property同样是响应式的,且触发视图更新

源码位置:src\core\observer\index.js

这里无非再次调用defineReactive方法,实现新增属性的响应式

关于defineReactive方法,内部还是通过Object.defineProperty实现属性拦截

代码实现
<div id="app">
    <p>msg===>{{msg}}</p>
    <p>sex===>{{json.sex }}</p>
<div/>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
 <script>
        let vm = new Vue({
            el:"#app",  
            data:{
                json:{
                    username:"张三",
                    age:20
                },
                msg:"hello"
            }
        })
        //vm.json.username = "李四"  修改属性可以
        //如果对之前存在的属性username,age进行更改是可以的,因为内部通过Object.defineProperty实现了数据双向绑定,但是对与后续新增添的属性例如sex,那么视图是没有办法渲染的
      //  vm.json["sex"]="男"  //发现页面不会渲染sex这个属性
      Vue.set(vm.json, "sex", "女")
</script>        

Object.assign()

直接使用Object.assign()添加到对象的新属性不会触发更新应创建一个新的对象,合并原对象和混入对象的属性

this.someObject = Object.assign({},this.someObject,{newProperty1:1,newProperty2:2...})

该方法用于将所有可枚举属性的值从一个或多个源对象(sources)分配到目标对象(target),并返回目标对象。

Object.assign(target, ...sources)
const target = { a: 1, b: 2 };
const source1 = { b: 4, c: 5 };
const source2 = { b: 6, c: 7 };
const obj = Object.assign(target,source1,source2);
console.log(obj); // (a: 1, b: 6, c: 7)
  1. 源对象属性与目标对象属性不同,则会被拷贝到目标对象中;

  1. 如果目标对象和源对象有相同的属性,目标对象的属性值会被源对象的属性值覆盖掉;

  1. 如果有多个源对象有相同的属性,那么目标对象的属性将会被最后一个源对象属性覆盖。

  1. Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。

  1. 注意:如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

  1. 如果只有一个参数,Object.assign会直接返回该参数。

$forceUpdate

如果你发现你自己需要在 Vue中做一次强制更新,99.9% 的情况,是你在某个地方做错了事

$forceUpdate迫使Vue 实例重新渲染

PS:仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。

forceUpdate就是重新render
  • 有些变量不在 state 上,但是你又想达到这个变量更新的时候,刷新 render;

  • state 里的某个变量层次太深,更新的时候没有自动触发 render。这些时候都可以手动调用 forceUpdate 自动触发 render 。所以建议使用 immutable 来操作 state ,redux 等 flux 架构来管理 state。

#小结

  • 如果为对象添加少量的新属性,可以直接采用Vue.set()

  • 如果需要为新对象添加大量的新属性,则通过Object.assign()创建新对象

  • 如果你实在不知道怎么操作时,可采取$forceUpdate()进行强制刷新 (不建议)

PS:vue3是用过proxy实现数据响应式的,直接动态添加新属性仍可以实现数据响应式

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值