Vue2.0中computed与watch的常见认知疑问及实践探讨
一、对「computed中不可执行异步操作」的疑问
通常认为computed里不能做异步,但我发现以下写法实际可正常运行:
computed:{ myComputed: function() { this.attributeB = this.asyncRequestMethod(this.attributeA) // 异步请求 return this.attributeA * 2 } }
当attributeA变化时,该计算属性能返回正确值,同时异步请求会更新attributeB,且无报错。我知道这种写法违背computed的语义(computed应是纯函数、无副作用),属于不推荐实践,而且如果页面没调用这个计算属性,异步请求不会触发。但我疑惑的是:这是否真的只是「不推荐」而非「禁止」?
另外我明确知道以下两种写法有问题:
computed: { myComputed: async function() { let returnVal = await this.asyncRequestMethod(this.attributeA) return returnVal }, myComputed2: function() { let returnVal = this.asyncRequestMethod(this.attributeA) return returnVal } }
前者会让computed返回Promise,无法在模板中正常渲染;后者直接返回Promise,同样无法得到预期的异步结果。
二、对「高负载计算应放在watch中」的疑问
常见说法是高负载计算适合放watch,但我查看源码后发现:computed的计算会被放入macrotask队列,watch的回调则在当前事件循环的microtask队列执行。但不管放哪个队列,高负载计算都会导致页面卡顿,我疑惑:
- 为什么说放入microtask队列更利于性能?
- 高负载计算放在watch真的比computed更优吗?
我的总结
我认为这两个常见认知并非「禁止性规则」,而是「推荐性实践」,希望得到解答或指出我的错误。
解答
关于computed中的异步操作
你说得没错,这确实是「不推荐」而非「禁止」——Vue并没有在语法层面阻止你在computed里执行异步副作用,只是这种写法完全违背了computed的设计初衷:
- computed的核心定位是「衍生状态」:它应该是基于现有响应式数据的纯计算,输出完全由输入决定,不应该产生任何副作用(比如修改其他属性、发送请求)。一旦引入副作用,组件的状态变化会变得不可预测,排查问题时很难追踪到
attributeB的更新来源。 - 触发时机不可控:如你所说,只有当computed被模板或其他响应式属性引用时才会执行,这会导致异步请求的触发时机完全依赖该计算属性的使用场景,很容易出现请求漏发的情况,业务逻辑变得脆弱。
而你提到的两种错误写法,本质是违背了computed需要返回同步的响应式值的要求,Vue无法将Promise作为响应式状态进行追踪和渲染,所以这两种写法属于「功能上不可用」,而非单纯的不推荐。
关于高负载计算的放置
首先纠正一个误解:Vue2中computed和watch的执行时机并非你说的macrotask/microtask的区别:
- computed的计算是懒执行+缓存的:只有当依赖变化且computed被引用时,才会同步执行计算(属于当前更新周期的同步任务,既不是macrotask也不是microtask)。
- watch的回调是异步执行的:默认情况下,watch的回调会被放入Vue的异步更新队列(属于microtask),在当前响应式更新周期结束后执行。
回到高负载计算的问题:
- 为什么watch更适合?:因为computed的计算是同步的,如果在computed里做高负载计算,当依赖变化时,计算会阻塞当前的渲染周期,导致页面直接卡顿;而watch的回调是异步执行的,它会等待当前渲染周期完成后再执行计算,至少能保证页面先完成当前的更新,不会出现明显的卡顿感。
- 但这也不是绝对的:如果高负载计算的结果需要直接在模板中展示,那还是得用computed,但此时应该考虑做计算缓存优化(比如用
_.memoize或者手动缓存结果),或者把计算拆分成多个小任务分批次执行。
最终结论
你对「禁止/不推荐」的判断是正确的:
- 「computed中不能执行异步」:其中返回Promise的写法是功能上不可用,而执行异步副作用的写法是不推荐(违背设计语义)。
- 「高负载计算放watch」:是基于执行时机的推荐实践,而非绝对规则,实际要根据业务场景选择。
内容的提问来源于stack exchange,提问作者Jiangqi Zhu

