likes
comments
collection
share

【Vue2】在vue中灵活赋予对象响应式

作者站长头像
站长
· 阅读数 12

昨天工作中遇到多个组件共用父组件传入的一个共同变量(在本文中统一用form指代),多人开发、业务复杂度上升后导致错误频发、难以调查,其中也不少原因是因为公司的低代码平台,导致开发者难以理解vue以及真正地上手vue。

【Vue2】在vue中灵活赋予对象响应式

首先简单定义一个sfc。

<template>
  <div>
    {{object1}}
  </div>
</template>

<script>
export default {
  data() {
    return {
      object1:{
        a:1,
        b:''
      },
      object3:{
        c:1,
        d:0
      }
    }
  },
  methods: {
  },
  created() {
  },
  mounted() {
  },
  updated() {
    console.log("视图发生变化")
  }
}
</script>

一、如何动态给对象添加响应式属性

1、单个或少量添加

使用this.$set(object,'x','content')Vue.set(object,'x','content')的方法可以对方法进行少量的添加,无论是在created还是在mounted或是methods方法中,都能生效。

注意:如果已经通过直接字面量的方法修改object对象,再set一个同名的key value值是不会生效的。

created(){
    this.object1.f = 1;
    // f会保持值为1,不会变为2,且无响应式
    this.$set(this.object1,'f',2);
}

2、批量添加

2.1、新建一个object对象,修改原有对象的引用

适用范围:

  • object2为当前组件内新建对象,其他地方没有引用过,或者并不是一个经过vue响应式处理过的对象。
  • 如果需要赋值给object1一个外部已经存在的、已经被其他画面进行使用的对象,请看下一种情况。
...
methods:{
    changeObject(){
        let object2 = {
          b:1,
          c:'1',
          e:{
            a:1
          }
        }
        this.object1 = object2
    }
}
...

2.2、已存在或已被使用中的对象,需要赋值给当前对象

深拷贝 + 修改对象引用。

适用范围:

  • 例如:父画面有一个form数据,你懒得新建一个object进行传值,或者是你必须要绑定一个在父画面使用中的对象。
...
methods:{
    changeObject() {
        let object2 = {};
        // 深拷贝的代码会在文后提供
        this.deepCopy(object2,this.object3);
        this.object1 = object2;
    }
}
...

2.3、保留当前对象的内容以及响应式,且合并传入对象

对于传入对象的值是通过深拷贝,重新建立的响应式,如需在当前环境操作传入对象,且影响传入处的响应式变化,直接对传入对象进行修改。

合并object + 深拷贝 + 修改对象引用

...
methods:{
    changeObject() {
        let object2 = {};
        let index = Object.assign({},this.object1,this.object3);
        this.deepCopy(object2,index);
        this.object1 = object2;
    }
}
...

3、深拷贝的几种写法

3.1、利用JSON api

无法拷贝属性里包括函数、Date对象等类型。

this.object1 = JSON.parse(JSON.stringify(this.object3))

3.2、自定义函数

    deepCopy(newObj,oldObj){
      for(var k in oldObj) {
        var item = oldObj[k]
        if(item instanceof Array){
          // 数组
          newObj[k] = []
          this.deepClone(newObj[k],item)
        } else if (item instanceof Object){
          // 对象
          newObj[k] = {}
          this.deepClone(newObj[k], item)
        } else {
          // 基本类型
          newObj[k] = item
        }
      }
    }

4、如何判断是否有响应式

例如直接操作一个没有提前在object1中声明的对象属性。

created(){
    this.object1.f = 1;
}

大概率是不会报错的,且如果该语句在created生命周期中时,页面上的object1会直接打印出来f的值,且你乍一看会觉得这就是有响应式了。但是当你要对f值进行动态修改的时候,发现视图并不会进行响应。

方法1:在console控制台中进行对象的打印,或者是debugger或者直接$0.__vue__.object1的方式进行查看。

方法2:在update中设置log,有打印出来就是视图有做更新。

注意:在下种情况下,update也会被触发,要注意。但是当前的响应式是$set触发的。

created(){
    this.object1.f = 1;
    this.$set(this.object1,'e',2);
}

5、对于第四点的视图变化解析

如果在crteated进行字面量的修改,视图发生变化(不准确),其实mounted的动作还没进行,所以对对象的修改是work的。

画面插值{{model}}双向绑定的el显示内容是怎么打印出来的呢?

vue使用对象的toString方法进行打印,所以解释了为什么无响应式的属性也能打印出来。

如果object1的内容换为下述:

object1:{
    a:{
        b:1
    }
}

这样控制台直接toString只会显示a:{b:[Object object]}

为什么画面能显示正常呢?

让我们给object重写一个toString方法,并且打进断点。

object1:{
    a:{
        b:1,
        toString(){
            debugger;
            return 'test'
        }
    }
}

这时候重新load画面的时候会在mounted之前进该debugger断点。

这时候进行前后调用栈查看,就可以清晰的看清vue的操作。vue会对data对象进行数据劫持,并且重写object原型上的toString方法。(说的应该不是很对)

vue的tostring方法,对应了对象的toString显示问题。

function toString(val) {
    return val == null
        ? ''
        : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
            ? JSON.stringify(val, null, 2)
            : String(val);
}