[前端][vue]检测变化的注意事项

由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。

尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。

对于对象

Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。例如:

var vm = new Vue({
  data:{
    a:1
  }
})

// `vm.a` 是响应式的

vm.b = 2
// `vm.b` 是非响应式的

对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。

但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property。例如,对于:

Vue.set(vm.someObject, 'b', 2);

您还可以使用 vm.$set 实例方法,这也是全局 Vue.set 方法的别名:

this.$set(this.someObject, 'b', 2);

有时你可能需要为已有对象赋值多个新 property,比如使用 Object.assign() 或 _.extend()。

但是,这样添加到对象上的新 property 不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。

// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 });

对于数组

Vue 不能检测以下数组的变动:

  • 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue

  • 当你修改数组的长度时,例如:vm.items.length = newLength

举个例子:

var vm = new Vue({
    data: {
        items: ['a', 'b', 'c']
    }
});
vm.items[1] = 'x'; // 不是响应性的
vm.items.length = 2; // 不是响应性的

为了解决第一类问题,以下两种方式都可以实现和 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 提供了 watch 方法,用于监听实例内 data 数据的变化。通常写法是:

new Vue({
 data: {
  count: 10blog:{
    title:'my-blog',
    categories:[]
  }
 },
 watch: {
  count: function (newVal, oldVal) {
   console.log(`new: %s, old: %s`, newVal, oldVal);
  }
 }
})

上述情况里 data 中的 count 属性可以直接监听,但是如果需要监听的数据是对象内的某一属性值的变化,直接 watch 对象 blog 是检测不到变化的,这是因为 blog 这个对象的指向并没有发生改变。

有几个解决方法

深度监测

new Vue({
 data: {
  count: 10blog:{
    title:'my-blog',
    categories:[]
  }
 },
 watch: {
  blog:{
    handler(newVal,oldVal){
      console.log(`new: ${newVal}, old: ${oldVal}`);
    },
    deep:true
  }
 }
})

里面的 deep 设为了 true,这样的话,如果修改了这个 blog 中的任何一个属性,都会执行 handler 这个方法。

不过这样会造成更多的性能开销,尤其是对象里面属性过多,结构嵌套过深的时候。

而且有时候我们就只想关心这个对象中的某个特定属性,这个时候可以这样

用字符串来表示对象的属性调用

new Vue({
 data: {
  count: 10blog:{
    title:'my-blog',
    categories:[]
  }
 },
 watch: {
  'blog.categories'(newVal, oldVal) {
    console.log(`new:${newVal}, old:${oldVal}`);
  },
 }
})

使用 computed 计算属性

new Vue({
 data: {
  count: 10blog:{
    title:'my-blog',
    categories:[]
  }
 },
 computed: {
  categories() {
   return this.blog.categories;
  }
 },
 watch: {
  categories(newVal, oldVal) {
   console.log(`new:${newVal}, old:${oldVal}`);
  },
 },
})
最后更新时间::
贡献者: 吴松泽