计算属性必须是纯函数,禁止副作用:它只应基于响应式依赖计算并返回值,不得修改状态、发请求或操作DOM,否则会破坏依赖追踪、缓存机制与响应式确定性。

因为计算属性的设计定位就是“只读取、只返回”,一旦掺入修改数据、发请求、操作 DOM 等行为,就会破坏响应式系统的确定性——这不是限制,而是保障。
计算属性本质是纯函数,不是执行逻辑的入口
Vue 的 computed 要求内部函数满足纯函数的两个核心条件:输入相同则输出一定相同;不修改外部状态,也不依赖不可控变量(比如 Date.now()、Math.random()、全局对象)。这和 JavaScript 里 add(a, b) { return a + b } 是一个逻辑层级:它不改 a 和 b,也不偷偷给 window.count 加 1。
如果在 computed 里调用 axios.get()、给 state.list.push(item)、或者直接 this.loading = true,就等于让“电子秤”一边称重一边削苹果——称出来的重量不准了,削掉的部分也没人知道。
副作用会直接干扰 Vue 的依赖追踪与缓存机制
计算属性的值会被缓存,仅当它所依赖的响应式数据变化时才重新求值。这个机制成立的前提是:求值过程本身不改变任何依赖项。一旦你在计算中修改了某个 reactive 对象的字段:
立即学习“前端免费学习笔记(深入)”;
Vue 3 组合式 API 与 最佳实践,涵盖代码组织、useXxx 模式、组合式函数设计、Store 集成、响应式优化及功能抽取。
- Vue 可能误判依赖关系,导致本该更新的没更新,或不该更新的反复更新
- 缓存失效,每次访问都重新执行,性能下降,还可能触发无限循环(比如 A 的 computed 改了 B,B 的 computed 又触发 A)
- 调试时难以复现问题——同一个 computed 在模板不同位置调用,结果却不一样
Vue 已经为你分好了工:什么该做,什么不该做
Vue 明确把“响应式数据流”和“副作用处理”做了职责分离:
- computed:负责“我现在是什么状态?”——适合格式化、过滤、组合、派生数据
- watch:负责“状态变了,我接下来要做什么?”——适合发请求、校验、同步外部系统、清理定时器
- methods:负责“用户点了,我立刻干点啥”——适合响应交互、触发业务动作
- onMounted / onBeforeUnmount:负责“组件生命周期里该搭的桥、该拆的线”
强行把 watch 的活塞进 computed,就像让交通信号灯自己去修红绿灯线路——它不是为这事设计的。
真实项目里最容易踩坑的“伪纯函数”
有些写法看起来没改数据,其实悄悄污染了输入:
- 对传入的数组调用 .sort() 或 .reverse() —— 原地修改,破坏原始数据
- 用 JSON.parse(JSON.stringify(obj)) 深拷贝,却忽略了 function、undefined、Date 等无法序列化的值
- 在 computed 里读取 this.$route.query 或 window.innerWidth —— 这些不是响应式依赖,但变化时 computed 不会自动重算
- 闭包中捕获了可变的局部变量(比如在 setup 里 let count = 0,然后 computed 里用了 count++)
这些都不是 Vue 的 bug,而是函数本身已不满足“纯”的定义。Vue 的报错 Unexpected side effect in computed function,其实是对你代码健壮性的一次善意提醒。

















