场景复现:为什么我的表格不更新?
项目是一个实时监控系统,前端用 Vue 3 + Composition API。核心逻辑是通过 WebSocket 推送的数据更新表格,而表格数据经过 computed 计算:
<span>const</span> tableData = <span>computed</span>(<span>() =></span> {
<span>return</span> rawData.<span>value</span>.<span>map</span>(<span><span>item</span> =></span> ({
...item,
<span>// 关键点:这里用到了外部工具函数</span>
<span>status</span>: <span>calculateStatus</span>(item.<span>timestamp</span>)
}))
})
问题来了:当 rawData 更新时,tableData 有时完全不触发重新计算。更诡异的是,直接在模板里写 rawData.map(...) 却能正常更新。你猜问题出在哪?
根因:computed 的依赖收集暗坑
computed 的响应式依赖是自动收集的,但它只认响应式数据内的属性访问。看看我们踩中的几个雷:
- 外部函数调用:
calculateStatus内部如果访问了非响应式变量(比如全局配置),这些依赖不会被computed追踪 - 隐式依赖丢失:当
calculateStatus内部逻辑变更(比如新增了Date.now()判断),computed仍然只认旧的依赖链 - 嵌套对象陷阱:如果
item.timestamp是嵌套对象且未被 reactive 包裹(比如从普通对象解构而来),其内部变化也不会触发更新
用 Vue 源码中的关键逻辑解释:computed 的 getter 执行时,会通过 effect 建立依赖关系,但只有在这个 getter 执行路径中被实际读取的响应式属性才会被记录。
解法:让依赖显式化
错误写法(隐式依赖)
<span>const</span> config = { <span>threshold</span>: <span>5</span> } <span>// 非响应式</span>
<span>function</span> <span>calculateStatus</span>(<span>timestamp</span>) {
<span>// 这里访问了外部非响应式变量 config</span>
<span>return</span> timestamp > config.<span>threshold</span> ? <span>'alarm'</span> : <span>'normal'</span>
}
正确写法(显式依赖)
<span>const</span> config = <span>ref</span>({ <span>threshold</span>: <span>5</span> }) <span>// 改为响应式</span>
<span>function</span> <span>calculateStatus</span>(<span>timestamp</span>) {
<span>// 通过 .value 触发依赖收集</span>
<span>return</span> timestamp > config.<span>value</span>.<span>threshold</span> ? <span>'alarm'</span> : <span>'normal'</span>
}
<span>// 或者用 computed 封装工具函数</span>
<span>const</span> status = <span>computed</span>(<span>() =></span> <span>calculateStatus</span>(rawTimestamp.<span>value</span>))
- 性能对比*:在 10,000 条数据的压力测试下,显式依赖写法比隐式依赖 + 强制更新(用
forceUpdatehack)的方案快 2.3 倍,因为后者会导致不必要的全量重新渲染。
避坑清单:computed 的三大天敌
- 外部工具函数:函数内若含有非响应式依赖,必须将其参数化或改为响应式对象
- 动态依赖路径:如
computed(() => obj[someKey.value])中someKey变更不会触发更新 - 异步操作:
computed内若有异步逻辑(比如await),必须用watchEffect替代
最佳实践:把 computed 当纯函数用
现在我强制团队遵守一条铁律:computed 内部应该像纯函数一样工作。所有依赖必须通过参数或响应式变量显式传入。遇到复杂逻辑时,拆分成多个 computed 比写一个"巨无霸"更可靠。
你可能会问:"那什么时候该用 watch 呢?" 我的经验法则:当你有副作用或需要基于前一个状态计算时(比如防抖),watch 更合适。而 computed 应该永远只是数据的映射。
最后留个思考题:你在项目中遇到过 computed 失灵的情况吗?是不是也和我一样,曾经以为 "Vue 的响应式系统会魔法"?欢迎在评论区分享你的踩坑故事。
这篇踩坑复盘把 computed 的依赖收集讲得清楚,适合 Vue 3 开发者排查表格不更新、响应式失效等问题,也给出了纯函数化与性能优化的落地思路。