Computed与Watch:Vue数据响应的双重神器

85 阅读6分钟

计算属性 Computed

计算属性是 Vue 中的一种属性,它的值是由一个或多个其他属性计算得出的。

计算属性的特点是:只有当它的依赖属性发生变化时,才会重新计算它的值。

想象一下你正在制作一个购物车应用,需要根据单价(price)和数量(quantity)计算总价(total)。你可以使用计算属性来做这件事:

new Vue({
  el: '#app',
  data: {
    price: 10,
    quantity: 5
  },
  computed: {
    total: function() {
      return this.price * this.quantity;
    }
  }
});

在这个例子中,total是一个计算属性,它的值是pricequantity的乘积。当pricequantity变化时,total的值会自动更新。

侦听器 Watch

侦听器是 Vue 的另一个特性,允许我们对数据的变化进行响应。

侦听器的特点是:当被侦听的数据发生变化时,它会触发一个函数。

watch 是一个对象,其中的键是观察的表达式,值是对应回调函数。值也可以是方法名,或者包含 handlerdeep 选项的对象。

基础用法

var vm = new Vue({
  data: {
    a: 1
  },
  watch: {
    a: function (newVal, oldVal) {
      console.log('a 的值从 ' + oldVal + ' 变为 ' + newVal + '!');
    }
  }
})

vm.a = 2 // a 的值从 1 变为 2!

这里,我们告诉 Vue "watch" 数据属性 a,当 a 发生改变时,执行我们的函数,这个函数接收两个参数,新值 newVal 和旧值 oldVal

高阶用法

深度监听

深度监听主要用于当我们需要观察一个对象的属性或者一个数组时。默认情况下,Vue只会对watch中的属性进行浅层观察,也就是说,如果一个对象的属性发生变化,watch并不会触发。同样的,如果一个数组的元素发生变化,watch也不会触发。只有当引用本身(也就是整个对象或整个数组)发生变化时,watch才会触发。

data: {
    obj: {
        key: ''
    }
},
watch: {
    'obj': {
        deep: true, //深度监听
        handler(newName, oldName) {
            console.log(newName);  //Watch执行
        }
    }
}

这样,我们深度监听了obj,无论是obj本身还是obj的属性key发生变化,watch都会触发。

请注意,因为在 Vue 2 中,由于使用 Object.defineProperty 来实现响应式,而这个方法无法监听到对象属性的添加或删除,以及无法精确地监听数组的变化,所以需要深度监听来观察嵌套对象或数组的变化。

而在 Vue 3 中,由于采用了 Proxy 来实现响应式,解决了 Vue 2 的这些问题。 所以,Vue 3 中不再需要使用深度监听来观察嵌套对象或数组的变化,因为这些变化默认就能被 Vue 3 的响应式系统捕获。

但是请再次注意!!!

深度监听在某些情况下仍然可能会用到。比如,当你需要观察的数据是由多个复杂对象构成的,每个对象的结构又不完全相同,你可能希望当任何部分发生变化时,都能得到通知。在这种情况下,深度监听可能仍然有用。但这种情况比较少见,大多数情况下,你可能不需要深度监听。

我们可以设想这样一个场景,你有一个应用,它有一个配置对象,这个配置对象由多个嵌套对象组成,每个对象都有很多属性。这个配置对象可能像这样:

let config = {
  user: {
    name: 'John',
    age: 30,
    address: {
      city: 'New York',
      country: 'USA'
    }
  },
  theme: {
    color: 'blue',
    fontSize: '14px'
  },
  // ...更多配置
}

现在,你希望当任何部分的配置发生变化时,都能得到通知,无论是 user 的 name 改变,还是 theme 的 color 改变,你都希望得到通知。这个时候,你就可以使用深度监听来实现这个需求:

watch(() => config, () => {
  console.log('Configuration has been changed');
}, { deep: true });

在这个例子中,无论配置对象的哪个部分发生变化,你都可以得到通知。注意,这个例子是假设你使用了 Vue 3,并且使用了 watch 函数。在 Vue 2 中,你可以使用 vm.$watch 方法来达到相同的效果。

立即执行

有的时候我们希望在Vue实例创建的时候就能够执行一次watch回调函数,而不是等到被观察的数据发生变化后才执行。这时候我们就可以设置immediatetrue

比如我们需要根据路由参数去发起一个请求,但是在页面刚加载的时候路由参数并没有发生变化,如果不设置immediatetrue,那么watch就不会触发,我们就无法发起初始请求。

data: {
    searchKeyword: ''
},
watch: {
    'searchKeyword': {
        immediate: true, //立即执行
        handler(newKeyword, oldKeyword) {
            // 发起搜索请求,哪怕在第一次加载页面时,即使关键词没有变化,也会发起一次请求
            this.search(newKeyword);  
        }
    }
},
methods: {
    search(keyword) {
        // 根据关键词发起搜索请求的逻辑
    }
}

取消观察

在某些情况下,我们可能需要在某个特定的时刻停止观察某个属性,这时候我们就可以利用watch返回的取消观察函数来停止观察。

例如,假设我们有一个实时搜索功能,用户在输入框输入关键词,我们就根据关键词去请求搜索结果。

但是当用户点击了某个搜索结果进入了新的页面后,我们就不需要再观察搜索关键词的变化了,这时候我们就可以调用取消观察函数来停止观察搜索关键词。

data: {
    searchKeyword: ''
},
created() {
    this.unwatch = this.$watch('searchKeyword', (newKeyword, oldKeyword) => {
        // 发起搜索请求
        this.search(newKeyword);  
    });
},
beforeDestroy() {
    // 在组件销毁前取消对searchKeyword的观察
    this.unwatch(); 
},
methods: {
    search(keyword) {
        // 根据关键词发起搜索请求的逻辑
    }
}

在这个例子中,我们使用了$watch方法来创建一个观察者,并把返回的取消观察函数保存在了this.unwatch中。然后在beforeDestroy钩子函数中调用了this.unwatch()来取消观察。因此当这个组件被销毁前,我们就停止了对searchKeyword的观察。

如果进入新页面,原页面销毁,那么这个组件的所有 watch 观察者都会自动停止。就不需要手动停止这个组件的 watch 观察者。

然而,有些情况下,你可能会需要手动停止一个 watch 观察者。

比如你有一个长期运行的 watch 观察者,它在整个应用的生命周期内都在运行,而不是只在某个特定的组件中运行。或者你的观察者注册在一个永远不会被销毁的组件上,比如 App.vue。在这种情况下,你可能会需要在某个特定的时间点停止这个观察者,而不是等待组件被销毁。

它们之间有什么区别呢?

首先,computed 是一个计算属性,它的值是根据其他响应式依赖进行计算的结果,并且这个结果会被缓存。

只有当依赖改变时,计算属性的值才会重新计算。而且 computed 的值可以直接在模板中使用,像普通属性一样。

然而,watch 是观察一个特定的值,当被观察的值变化时,执行一个函数。

这个函数不返回新的值,它的目的通常是执行一些操作(如 API 调用或其他副作用),而不是计算新的值。在 watch 的函数中返回的值并不能像 computed 那样直接在模板中使用。

这就是为什么说,虽然在某些情况下,computed 和 watch 可以实现相同的功能,但它们的主要用途是不同的。

computed 更适合需要计算新值的情况,而 watch 更适合需要在值变化时执行特定操作的情况。

举个例子来说,假设我们有一个价格(price)和数量(quantity),我们想得到总价(total)。我们可以使用计算属性来做这个:

computed: {
  total() {
    return this.price * this.quantity;
  }
}

在这里,我们并不关心 price 或 quantity 何时变化,我们只关心 total 的值。所以,使用计算属性是最合适的。而且,我们可以像使用普通属性一样在模板中使用 total。

但是,如果我们需要在 price 或 quantity 变化时执行某个操作(比如发送一个 API 请求),那么就应该使用 watch:

watch: {
  price(newPrice, oldPrice) {
    // 当 price 变化时,执行一些操作
  },
  quantity(newQuantity, oldQuantity) {
    // 当 quantity 变化时,执行一些操作
  }
}

在这里,我们并不关心新的值是什么,我们只关心值何时变化,并且在值变化时执行一些操作。这就是为什么在这种情况下,使用 watch 更合适。

总的来说,你可以把 computed 理解为一个计算的值,而 watch 则是一种响应值变化的方式。