computed 依赖的是响应式引用而非值内容,对象整体替换会因引用变更触发重新计算;应通过 Object.assign 或属性赋值保持引用稳定以避免误触发。

当 computed 的依赖项是一个响应式对象(比如 reactive 或 ref 包裹的对象),而你**没有访问其内部属性,只是整个对象被替换成一个新对象**时,computed 仍会重新计算——这不是缓存“失效”,而是依赖确实发生了变化。
依赖追踪只看“谁被读取”,不看“值是否相同”
Vue 的响应式系统在 computed 初始化时,仅收集在 getter 中**实际读取过**的响应式属性。如果你的计算属性写成:
const userInfo = reactive({ name: 'Alice', age: 30 })
const displayName = computed(() => userInfo.name)
那么 Vue 只追踪 userInfo.name 这个路径。即使你后续执行 userInfo = reactive({ name: 'Alice', age: 30 })(新建一个对象),由于 userInfo 本身被重新赋值,原 userInfo 的引用已丢失,getter 再次执行时会读取新对象的 name,触发新的依赖收集 —— 此时依赖项已变更,必然重新计算。
对象替换 → 响应式引用变更 → 依赖链断裂
关键点在于:computed 依赖的是响应式数据的引用,不是它的结构或内容。以下情况都会导致重新计算:
-
userInfo = reactive({...old})(创建新 reactive 对象) -
userInfo = ref({...}).value(用 ref 替换,且未保持响应式引用) -
Object.assign(userInfo, newData)不会触发(因为没换引用),但userInfo = {...userInfo, ...newData}会(换了引用)
如何避免误触发?保持引用稳定
如果业务上需要“逻辑上更新对象但不想让 computed 重算”,应避免替换顶层响应式引用,改用:
-
Object.assign(userInfo, newData)(直接修改属性,引用不变) for (const key in newData) userInfo[key] = newData[key]- 使用
shallowRef+ 手动triggerRef控制更新时机(适合深层结构不变、只关心顶层变更的场景)
注意:即使新旧对象 JSON.stringify 完全一致,只要引用不同,Vue 就认为依赖变了 —— 因为它无法也不应该做深比较,那是开发者职责。
对比:内部属性变 vs 整个对象换
假设 userInfo 是 reactive({ name: 'A', job: { title: 'Dev' } }):
- 写
computed(() => userInfo.job.title):只追踪job.title;改userInfo.job.title = 'Eng'→ 重算;但userInfo.job = { title: 'Eng' }→ 也重算(job引用变了) - 写
computed(() => userInfo)(直接返回整个对象):不推荐,但若真这么写,任何对userInfo的赋值都会触发重算,哪怕新对象内容一模一样
根本原则没变:computed 的“依赖”是 getter 执行过程中触达的响应式节点及其访问路径,不是值的语义等价性。


















