vue 3 中 ref 本身不会导致循环引用死循环,真正原因是副作用函数中同步读取并修改同一 ref 路径,形成“读-改”闭环;典型错误如 watch 直接修改监听源、watcheffect 内改依赖 ref、computed setter 中读取自身值等。

Vue 3 中 ref 本身不会直接导致循环引用死循环,真正引发响应式死循环的,是 在副作用函数(如 watch、watchEffect、effect 或 computed getter)中,读取并同步修改了被监听的同一 ref 路径。ref 只是容器,问题出在“读-改”闭环没断开。
watch 中直接改监听源
这是最典型的错误写法:
❌ 错误示例const count = ref(0)watch(() => count.value, () => { count.value++ }) // 一改就触发自己,无限循环
每次 count.value 改变,watch 就执行回调;回调又改 count.value,再次触发 watch……形成闭环。
✅ 正确做法:
- 用 computed 表达派生逻辑,避免手动赋值:例如
const doubled = computed(() => count.value * 2),只读不写 - 若必须更新状态,确保更新的是另一个独立 ref,或加条件防护:例如
if (count.value - 需要双向同步时,用
computed({ get, set })显式分离读写职责,set 里不做无条件反向赋值
watchEffect 自动追踪 + 同步修改
watchEffect 会自动收集内部所有响应式依赖,风险更高:
const flag = ref(false)watchEffect(() => { flag.value = !flag.value }) // 一进就改,改完立刻重跑
首次运行 → flag 变 true → 触发重新运行 → flag 变 false → 再次运行……浏览器直接卡死或报错 “Maximum recursive updates”。
审查 Vue 管理后台的变更请求(Vue 2 + Element UI,Vue 3 + Element Plus)。重点关注最小化变更实现、上线风险检查以及回归检查清单生成。
✅ 正确做法:
- 避免在 watchEffect 内部直接修改它所依赖的 ref。把它当作“观察者”,不是“控制器”
- 改用 watch 监听明确来源,并在回调中控制更新时机(如配合防抖、节流、条件判断)
- 如需初始化同步,可先用
onMounted或nextTick延迟一次,避开初始依赖收集阶段
computed 的 setter 触发自身 getter
当 computed 配置了 setter,且 setter 内部又读取了该 computed 的值,也可能形成隐式循环:
❌ 错误示例const source = ref(1)const derived = computed({ get: () => source.value * 2, set: (v) => { source.value = derived.value / 2 } // 读 derived.value → 触发 get → 再次进入 set})
✅ 正确做法:
- setter 中只操作源头 ref,不读取当前 computed 的值;例如
set: (v) => { source.value = v / 2 } - 若逻辑复杂,拆成两个 ref + 一个 watch,让数据流向单向清晰
深层嵌套 ref 引发的间接循环
比如把 ref 放进 reactive 对象里,又在 watch 中解构后修改:
❌ 错误示例const state = reactive({ countRef: ref(0) })const { countRef } = toRefs(state) // 解构后仍是 refwatch(countRef, () => { countRef.value++ }) // 同样触发自身
本质没变:监听 ref.value,又改 ref.value。
✅ 正确做法:
- 优先用 reactive 管理结构化数据,ref 专注原子值;避免混合嵌套 ref 在 reactive 里
- 如必须嵌套,watch 时监听原始路径
() => state.countRef.value,并在回调中明确区分读写目标 - 用
toRaw或markRaw排除不需要响应式的对象,减少意外追踪
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!









