Vue中数组更新后视图不动态更新原因剖析

在Vue的官方文档有提到这样一个注意事项:

由于 JavaScript 的限制,Vue 不能检测以下变动的数组:
1)当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
2)当你修改数组的长度时,例如:vm.items.length = newLength

 也就是说,直接设置数组的某一项的值,虽然改变了数组的值,但视图上显示的仍为数组之前的值,数据的响应失效了。出现这种现象的根本原因是什么呢?

首先我们先来了解vue数据响应的原理。官方文档的解释:

当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。

也就是说当改变data中属性的值时会触发其相应setter的调用,从而实现响应的操作。但getter和setter是有局限性的。我们先来看下面的这个例子:

var person = {};
Object.defineProperties( person, { 
      age: { 
        defaultValue: 11, 
        get: function () {
          return this.defaultValue;
        }, 
        set: function (val) { 
          this.defaultValue = val; 
          console.log("触发了set") 
        }
      } 
    });

// 修改属性的值时能够触发set
person.age = 12    // 触发了set
->触发了set
->12

person.age
->12

// 将属性的值设置为一个数组,当通过索引值修改数组的某一项或使用数组的某些方法修改数组时不能触发set
person.age = [2,3,4]    // 触发了set
->触发了set
->(3) [2, 3, 4]

person.age[2] = 5      // 未触发set
->5

person.age
->(3) [2, 3, 5]

person.age.push(5)     // 未触发set
->4

person.age
->(4) [2, 3, 4, 5]


// 将属性的值设置为一个对象,当修改对象中某属性的值时无法触发set
person.age = { first: 1 }
->触发了set
->{first: 1}

person.age.first = 2      // 未触发set
->2

通过上述例子可以观察得出:

当该属性的值为一个数组时,通过索引修改数组某一项的值或使用数组的某些方法修改数组并不能触发set;当属性的值为一对象时,直接修改对象中属性的值时也无法触发set。

有问题就相应的有解决方案。官方文档给出的解决方案如下:

为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue 相同的效果,同时也将触发状态更新:

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)

// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)


你也可以使用 vm.$set 实例方法,该方法是全局方法 Vue.set 的一个别名:

vm.$set(vm.items, indexOfItem, newValue)

 

为了解决第二类问题,你可以使用 splice

vm.items.splice(newLength)

而对于对象,当修改对象的属性或为对象添加属性时应该使用以下方法:

Vue.set(vm.userProfile, 'age', 27)

vm.$set(vm.userProfile, 'age', 27)

有时你可能需要为已有对象赋予多个新属性,比如使用 Object.assign() 或 _.extend()。在这种情况下,你应该用两个对象的属性创建一个新的对象。所以,如果你想添加新的响应式属性,则使用后一种方法来替代前一种方法:

// 不要使用此方法
Object.assign(vm.userProfile, {
  age: 27,
  favoriteColor: 'Vue Green'
})

// 应该使用此方法
vm.userProfile = Object.assign({}, vm.userProfile, {
  age: 27,
  favoriteColor: 'Vue Green'
})

额外的:由于数据响应原理机制, Vue 不允许动态添加根级响应式属性,所以你必须在初始化实例前声明所有可能用到的根级响应式属性,且为这些属性都设一个初值,哪怕只是一个空值。

相关官方文档的说明:

数组更新检测注意事项

对象更改检测注意事项深入

响应式原理

猜你喜欢

转载自blog.csdn.net/m0_37747665/article/details/82148966