Vue 里 computed 和 watch 到底怎么选:别把两种能力写成一回事

0 阅读

刚开始用 Vue 时,很多人都会把 computedwatch 混着用。

表面上看,它们都能“跟着数据变化做点事”,写起来也都不难。可项目一大,差别就会越来越明显。该放在 computed 里的逻辑写进了 watch,后面会越来越绕;反过来,把副作用塞进 computed,代码也会慢慢变得不好维护。

这两个能力看着相近,职责其实完全不同。

先说最短的结论

如果你只是想基于已有状态推导出一个新值,用 computed

如果你是想在某个值变化后执行副作用,比如发请求、写本地存储、调第三方接口,用 watch

这个边界一旦立住,很多代码会立刻顺起来。

computed 更像“结果”

computed 的核心是派生状态。

例如购物车里你已经有:

  • 商品列表
  • 数量
  • 单价

那总价就很适合放进 computed

1computed: {
2  totalPrice() {
3    return this.items.reduce((sum, item) => {
4      return sum + item.price * item.count;
5    }, 0);
6  }
7}

这个值不需要手动维护,也不需要额外存一份。只要原始数据变了,结果自然就变。

这就是 computed 最舒服的地方:它让状态表达更完整,而不是让你再多维护一份变量。

watch 更像“反应”

watch 则不是拿来推导结果的,它更像对变化的响应。

比如用户切换搜索条件后,你要重新拉一遍列表:

1watch: {
2  keyword(newValue) {
3    this.fetchList(newValue);
4  }
5}

这里真正有意义的不是“生成了一个新值”,而是“变化发生后,要去做一件额外的事”。

请求、日志、埋点、缓存同步,这些都属于 watch 更擅长的范围。

为什么很多代码会把它们写反

因为 watch 看起来什么都能做。

你完全可以监听某个字段变化,再手动去改另一个字段,于是代码慢慢变成这样:

1data() {
2  return {
3    firstName: 'Su',
4    lastName: 'Jf',
5    fullName: ''
6  };
7},
8watch: {
9  firstName() {
10    this.fullName = this.firstName + ' ' + this.lastName;
11  },
12  lastName() {
13    this.fullName = this.firstName + ' ' + this.lastName;
14  }
15}

功能没问题,但结构已经开始别扭了。

因为 fullName 并不是独立状态,它只是另外两个值的组合。既然只是组合,就不该手动维护。

这种写法改成 computed 会更自然,也更不容易漏掉边界情况。

一个简单判断办法

可以问自己一句话:

“这个值是要算出来,还是要顺手做点别的?”

如果答案是“算出来”,优先 computed

如果答案是“要顺手去做请求、写缓存、触发外部逻辑”,那就是 watch

别让 watch 变成补丁桶

项目里有个很常见的现象:一开始只是监听一个字段,后面慢慢加条件、加分支、加节流、加异步请求,最后一个 watch 变成了功能中心。

这并不是 watch 的错,而是它太容易被拿来打补丁。

所以在写 watch 时最好克制一点,只让它做“变化后的动作”,不要拿它承担状态建模。

写在最后

computedwatch 都很重要,但它们处理的问题不是一类。

一个是“结果”,一个是“反应”。当你不再把它们混着用,Vue 代码会轻很多,状态关系也会更清楚。