先记住这个答案
computed 根据计算过程中读取的响应式依赖缓存结果,在依赖未改变时重复读取可以复用结果。模板里的普通方法调用则在对应渲染表达式执行时重新运行。计算昂贵、被多处读取且依赖变化相对少时,computed 更有意义;需要不同参数、执行用户动作或成本很低的逻辑不必一律改成 computed。其 getter 应保持纯粹,也不能依赖没有响应式来源的时间值自动刷新。
- 缓存是否可复用由响应式依赖决定
- 模板方法在对应表达式执行时运行
- computed getter 负责派生值,不承担副作用
同一份筛选结果被多处读取时的差异
假设商品页面同时展示符合条件的商品列表、数量和总价。如果每个区域都调用同一个筛选方法,相关渲染表达式执行时可能重复遍历输入。将筛选结果定义为 computed,可以把同一组依赖下的结果作为统一派生值供多个位置读取,减少重复计算和规则分散。
如果列表很小、方法只做一个简单格式化,缓存收益可能并不明显。还要看依赖是否每次交互都改变:搜索词每次输入都变化时,筛选结果当然需要更新。computed 能复用未失效的结果,不能消除业务上本来必须执行的计算,也不能保证巨量列表渲染没有其它瓶颈。
带不同参数的试算不等于一个计算属性
假设页面允许分别试算折扣百分比,输入参数来自用户点击的多个预设。普通函数接收商品与折扣参数会更清晰;computed 本身不是为任意参数组合自动维护多份缓存的通用函数。如果它返回一个函数,也不能因此断言那个函数每次调用的结果都被 computed 分参数记忆了。
相反,当“当前选中的折扣比例”已经保存在响应式状态中,最终价格就是这个比例与商品价格的派生结果,此时 computed 很自然。应先决定参数是本次操作的输入,还是持续驱动界面的状态,再决定函数与计算属性的位置,避免为了使用缓存而扭曲数据模型。
时间、异步请求和原地修改带来的误判
computed 中只读取 Date.now 不会建立一个会随时间流逝触发更新的响应式依赖。想展示时钟,需要有明确更新的响应式时间状态,并按生命周期管理定时器。外部普通变量和绕过代理的变化也不能被当成 Vue 已经追踪到的依赖。
getter 应只根据输入产生结果,不应在里面发请求、修改其它 state 或改变 DOM。异步请求的加载、失败、取消和竞态需要独立建模。对输入数组排序等操作也应避免原地修改源数据;如果派生计算改变了自己的输入,调试时就很难再清楚区分是什么变化导致重新计算。
容易答错的地方
- 认为 methods 每一帧都会执行
- 方法执行取决于是否实际调用。模板中使用的方法会随着相关渲染表达式执行,而不是浏览器每一帧无条件运行;普通事件方法则通常在相应事件发生时运行。
- 认为 computed 缓存能修复错误的依赖来源
- 缓存需要基于响应式依赖才能正确失效。非响应式输入、代理之外的修改或错误引用不会因为包上一层 computed 就自动变成可靠的数据更新通道。
面试官还会怎么问?
依赖变化后 computed 是否必须立即执行 getter?
不要把失效与每个时点的实际求值混为一谈。computed 通常按读取需求提供最新值,具体通知与优化还受框架版本影响;业务上应依赖结果正确,而不是利用 getter 执行次数安排副作用。
可以在 computed 里调用纯工具函数吗?
可以。依赖追踪关注计算期间发生的响应式读取,纯工具函数也可以参与计算。应保证函数不写外部状态,并让输入与输出的关系清晰,便于单独验证算法。
只想格式化当前行一个金额,有必要 computed 吗?
不一定。简单格式化函数通常足够,尤其是需要接收每行不同金额时。只有出现明确重复计算成本,或该派生值本身需要被多处持续消费,才值得进一步设计缓存方式。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。