你的浏览器无法正常显示内容,请更换或升级浏览器!

Vue3 响应式避坑:ref 与 reactive 的边界

tenfei
tenfei
发布于2026-10-12 12:24 阅读16次
Vue3 响应式避坑:ref 与 reactive 的边界
数据返回了界面却不更新?本文从一个真实 bug 出发,讲清 Vue 3 中 ref 与 reactive 的适用边界、解构丢失响应性的原因、watch 的三个常见误用,以及 props 解构的陷阱,最后给出一条快速判断口诀与排查思路。
## 一个诡异的 bug 后台列表页,明明数据已经返回,但表格有一行显示的还是旧值。点进详情再返回,又正常了。最后定位到原因:用 `reactive` 包了一个数组,往里面 `push` 新对象后,模板没更新。 问题出在我把 `reactive` 的返回值解构了出来。这篇文章把 Vue 3 响应式里最容易踩的边界一次讲清。 ## ref 与 reactive 的选择 **`ref`** 适合基本类型,也适合"整体替换"的对象;**`reactive`** 适合需要深度监听的大对象。但 `reactive` 有三个限制,记住这三条就能避开九成问题: 1. **解构即丢失**。`const state = reactive({ a: 1 }); const { a } = state;` 之后 `a` 就是个普通数字,改了不触发更新。 2. **整体赋值即丢失**。`state = { a: 2 }` 这种写法不会生效——`reactive` 返回的引用不能整体替换。 3. **不能包 Map/Set 之外的非普通对象**。Date、class 实例放进 `reactive`,代理行为不可预期。 实践经验:**默认用 `ref`,只有明确需要深度代理时才用 `reactive`**。`ref` 在 `.value` 层面的限制更少,心智负担也小。 ## 为什么 push 之后没更新 我当时的代码大概是这样的: ```ts const list = reactive<Item[]>([]); const load = async () => { const { data } = await useFetch('/api/list'); // 错误写法:整体替换 Object.assign(list, data.value); // 这个其实是对的 }; ``` 真正出问题的是另一处:在 `setup` 外定义了一个普通数组 `rawList`,组件里 `reactive(rawList)` 之后,`rawList` 本身仍然是非响应式的,我在工具函数里直接改 `rawList.push(...)`,界面当然不动。 **核心原则:响应式只在代理对象上生效。谁被代理,就改谁。** ## watch 的三个坑 **坑一:watch 源是 `reactive` 时默认深度监听,`ref` 不是。** ```ts const obj = ref({ a: 1 }); watch(obj, (v) => { /* 只在 .value 整体替换时触发 */ }); // 需要监听内部属性时: watch(obj, (v) => {}, { deep: true }); watch(() => obj.value.a, (v) => {}); // 更推荐,精准且便宜 ``` **坑二:watchEffect 里的异步副作用要自己清理。** 组件卸载后 Promise 才 resolve,再去改状态会报 warning。用 `onCleanup` 或者改成 `watch` 的显式写法更稳。 **坑三:immediate 与 deep 别乱加。** 很多人给每个 watch 都加 `deep: true`,大对象递归遍历一遍,性能直接崩。只对真正需要深度监听的对象开启。 ## props 解构的陷阱 Vue 3.5 之前,`defineProps` 解构出来的 props 会**丢失响应性**: ```vue <script setup> const { id } = defineProps(['id']); // 旧版里 id 变化模板不更新 </script> ``` 稳妥做法是不解构,或者用 `toRef`: ```ts const props = defineProps<{ id: string }>(); const id = toRef(props, 'id'); ``` ## 一条判断口诀 > 想知道某个值改了会不会更新,就问自己:**它是不是从 setup 里"流"出去的响应式源?** 如果是解构出来的、赋值给普通变量的、在工具函数里新建的,都不会更新。把这条记住,排查响应式 bug 的时间能少一半。 ## 最后 Vue 3 的响应式设计整体比 Vue 2 清晰,但 `ref` / `reactive` / `toRef` 三兄弟的边界需要花点时间适应。遇到"改了不更新",先打印一下这个值是不是 Proxy、是不是已经脱离了响应式链路,比盲目加 `deep: true` 有效得多。

2

0

文章点评
tenfei
tenfei发文310篇,2人关注Ta.
赞助商广告位
Copyright © from 2021 by namoer.com
458815@qq.com QQ:458815
蜀ICP备2022020274号-2