Vue3 项目踩坑笔记:响应式丢失的六个真实场景

发布于2026-09-11 10:08 阅读15次 用 Vue3 组合式 API 写页面,最头疼的就是数据改了视图不更新。本文汇总我在真实项目中遇到的六个"响应式丢失"场景:解构 reactive、ref 忘记 .value、数组下标赋值、props 直接改、异步里丢引用、以及 shallowRef 的误用,每个都给出原因和正确写法。
# 一、响应式不是万能的
Vue3 的响应式基于 Proxy,比 Vue2 的 defineProperty 强很多,但**仍有明确的失效边界**。下面六个场景我都在项目里真实踩过,改完之后页面终于正常刷新。
# 二、六个真实场景
## 1. 解构 reactive 对象
```js
const state = reactive({ count: 0 })
const { count } = state // 解构后失去响应式
```
解构出来的是普通值。正确做法用 `toRefs`:
```js
const { count } = toRefs(state) // 保持响应式
```
## 2. ref 在 script 里忘记 .value
模板里自动解包,但 `<script setup>` 里必须 `.value`。这个坑最容易在 `if (loading)` 这种判断里出现——**空对象 truthy**,逻辑永远走错分支。
## 3. 直接给数组下标赋值
`arr[0] = x` 在 Vue3 里其实是响应式的(Proxy 覆盖到了),但如果数组来自 `Object.freeze` 或 props 传入的只读数组,就不会更新。用 `splice` 更稳妥。
## 4. 直接修改 props
```js
props.list.push(item) // 子组件改父数据,反模式
```
要么 emit 事件让父组件改,要么在子组件里用 `const local = ref([...props.list])` 建立本地副本。
## 5. 异步回调里丢了引用
在 setTimeout / Promise 里拿到的是**旧的解构值**,即使原对象变了也不会触发。解决办法:回调里始终通过对象访问 `state.xxx`,而不是提前解构。
## 6. shallowRef 误用
`shallowRef` 只监听 `.value` 整体替换,内部属性变化不触发。适合存大型只读数据,但如果当成 `ref` 用来改内部字段,就会"改了不动"。
# 三、通用排查套路
1. 先确认数据源类型:ref / reactive / props / 普通值;
2. 打开 Vue DevTools 看数据面板,**值变了但视图没动**基本就是响应式断裂;
3. 优先用 `toRefs`、`computed`、`watch` 这些官方 API,少走非响应式的捷径。
# 四、小结
Vue3 的响应式失效几乎都源于"数据经过了非响应式的中间层"——解构、赋值、冻结、浅层代理。记住这句话,九成这类 bug 都能秒定位。