vue响应式的误区

216 阅读2分钟

背景

最近项目组使用vue代替angularjs,在响应式这一块有很多同事用的不太对,导致数据修改后,页面看起来没有同步“渲染”。下面我写了一个小例子,希望看完以后加深对vue响应式的理解。

对数组元素的修改

1. 直接修改数组元素

首先看如下例子:

<template>
  <div id="app">
    <h2>测试直接修改简单元素数组的值</h2>
    <button @click="setArr1">setArr1</button>
    <div>{{arr1}}</div>
    
    <h2>测试直接修改复杂元素数组的值</h2>
    <button @click="setArr2">setArr2</button>
    <div>{{arr2}}</div>
    
  </div>
</template>
export default {
  name: 'App',
  data() {
      return {
          arr1: [1,2,3,4],
          arr2: [{'age': 12, name: 'tom'}, {'age': 100, name: 'jerry'}],
      }
  },
  methods: {
      setArr1() {
          this.arr1[0] = 99
      },
      setArr2() {
          let tom = this.arr2[0]
          tom.age = 99
      },
  }
}

首先我们有2个数组:

它们分别是由数字组成的简单数组arr1,和由对象组成的数组arr2.点击setArr1和setArr2按钮可以修改它们首位元素的值,修改方式是直接修改,看代码:

下面通过动图来查看修改后的结果:

可以看到:点击setArr1按钮,数组[1,2,3,4]没有被修改成[99,2,3,4]。但是点击setArr2按钮,数组arr2的第一个元素的age属性确实被修改成了99.

此时发现arr1数组的值也发生了变化。这就是由于错误的修改arr1的数据导致的,具体的原因暂不清楚。

2. $set修改数组元素

<template>
  <div id="app">
    <h2>测试$set修改简单元素数组的值</h2>
    <button @click="setArr3">setArr3</button>
    <div>{{arr3}}</div>

    <h2>测试$set修改复杂元素数组的值</h2>
    <button @click="setArr4">setArr4</button>
    <div>{{arr4}}</div>
    
  </div>
</template>
export default {
  name: 'App',
  data() {
      return {
          arr3: [1,2,3,4],
          arr4: [{'age': 12, name: 'tom'}, {'age': 100, name: 'jerry'}],
      }
  },
  methods: {
      setArr3() {
          this.$set(this.arr3, 0, 99)
      },
      setArr4() {
          let tom = this.arr4[0]
          tom.age = 99
          this.$set(this.arr4, 0, tom)
      },
  }
}

这次使用$set来修改数组元素,下面上动图:

这次可以看到,点击按钮后数组元素的修改都成功了。

3. 修改数组元素的总结

其实很简单,就2种情况:

  1. 数组元素是numberstring基本类型时,要用$set来修改
  2. 数组元素是objectarray等复杂类型时,可以直接用"."或者中括号[]来进行赋值操作。当前,用$set也是可以的。

进阶:对对象属性的修改

看如下例子:

<template>
  <div id="app">
    <h2>测试直接给对象新增属性</h2>
    <button @click="setAttribute1">setAttribute1</button>
    <div>{{obj1}}</div>

    <h2>测试$set给对象新增属性</h2>
    <button @click="setAttribute2">setAttribute2</button>
    <div>{{obj2}}</div>
  </div>
</template>
export default {
  name: 'App',
  data() {
      return {
          obj1: {name: 'kaizige', age: 18},
          obj2: {name: 'wangrun', age: 81},
      }
  },
  methods: {
      setAttribute1() {
          this.obj1.height = Math.random() * 100
      },
      setAttribute2() {
          this.$set(this.obj2, 'height', Math.random() * 100)
      },
  }
}

这个例子是想给对象增加一个新的height属性,setAttribute1setAttribute2方法分别是通过直接用"."操作符来添加属性和使用$set来添加属性,下面看动图:

很明显,直接给对象添加属性,vue是无法检测到的(响应失效了),但是$set可以。

点击setAttribute2obj1也发生了变化。这同样是由于错误的修改obj1的数据导致的,和上面的例子类似。

因此,对于对象属性的动态添加,必须使用$set方法。

总结

vue的响应式可以检测到已知属性的修改,对于新的属性,如果我们不通过$set方法去通知vue,vue是无法检测到的。这其中更深入的原因需要了解vue响应式的原理。