一、解构丢失响应:99% 的"页面不更新",都死在这一步 😭
1️⃣ 经典翻车现场(新老手通杀)
ts
const state = reactive({
count: 0,
name: 'Tom'
})
const { count } = state
count++ // ❌ 页面不更新
很多人第一反应:
"Vue 又抽风了?"
不,Vue 很无辜 😅。
2️⃣ 本质原因:你把"代理"拆成了"普通值"
reactive的响应式能力,来自 Proxyconst { count } = state做的事情是:
👉 读取一次值,赋给一个普通变量
之后这个 count:
- ❌ 不再经过 Proxy
- ❌ 不再参与依赖收集
- ❌ 和
state.count再无关系
👉 响应式链路当场断裂。
人话版:
你把摄像头拆下来放桌上,然后指望它还能实时监控 😅
3️⃣ 正确解法:toRef / toRefs(别硬扛)
✅ toRef:拿一个字段出去用
ts
const count = toRef(state, 'count')
count.value++
✅ toRefs:整体解构还能保响应
ts
const { count, name } = toRefs(state)
count.value++
一句口诀:
reactive 一旦要解构,
不 toRefs,必翻车。
二、数组与对象的响应式注意点:不是不能改,是"怎么改"🧨
1️⃣ 数组:这些写法你得格外小心
❌ 直接改索引(某些复杂场景不触发更新)
ts
list[index] = newValue
在 Vue3 里大多数情况下是 OK 的,
但一旦你在 shallowRef / 复杂依赖 / 计算属性缓存 场景中,这种写法非常容易"看起来改了,UI 没动"。
✅ 更稳的写法
ts
list.splice(index, 1, newValue)
或者
ts
list.value = [...list.value]
👉 数组更新要么用"变更 API",要么"整体替换"。
2️⃣ 对象:别在 reactive 上玩"整体替换"
ts
const user = reactive({ name: 'Tom' })
user = { name: 'Jerry' } // ❌ 直接断响应式
原因很简单:
reactive返回的是 Proxy- 你这一赋值,是把 Proxy 整个换掉了
✅ 正确方式
ts
user.name = 'Jerry'
或者一开始就用 ref
ts
const user = ref({ name: 'Tom' })
user.value = { name: 'Jerry' } // ✅
经验法则:
需要整体替换对象 → 用 ref
只改属性 → 用 reactive
三、深层 watch:不是不能用,是"别乱用"😵💫
1️⃣ 深层 watch 的典型"自杀式写法"
ts
watch(
() => state,
() => {
// 业务逻辑
},
{ deep: true }
)
你可能觉得:
"这样最保险,改哪都能监听。"
但 Vue 内心是:
"你这是让我盯着整个宇宙啊兄弟 🫠"
2️⃣ 深层 watch 的真实代价
- 每次依赖触发,都会 递归遍历整个对象
- 对象越大,成本越高
- 很多时候你 根本只关心其中一小部分
👉 性能问题不是立刻炸,而是慢慢把页面拖垮。
3️⃣ 正确姿势:watch 具体字段
❌ 错误示例
ts
watch(form, validate, { deep: true })
✅ 推荐写法
ts
watch(
() => [form.username, form.password],
validate
)
或者拆成多个:
ts
watch(() => form.username, validateUsername)
watch(() => form.password, validatePassword)
一句狠话:
deep watch 不是兜底方案,是最后手段。
四、那些"看不见的响应式陷阱",你很可能已经踩了 😬
1️⃣ watchEffect 无脑用
ts
watchEffect(() => {
doSomething(state)
})
- 依赖不透明
- 很容易"多收集、多执行"
- 调试困难
👉 复杂逻辑优先用 watch,而不是 watchEffect。
2️⃣ 把"不变的东西"也变成响应式
ts
const config = reactive({
PAGE_SIZE: 20
})
❌ 浪费性能
❌ 增加心智负担
✅ 直接用普通对象 / const
3️⃣ 响应式对象层级太深
ts
const big = reactive({
a: { b: { c: { d: {} } } }
})
如果你只关心最外层:
👉 shallowReactive / shallowRef 才是正解。
五、解决方案汇总:一张"避坑速查表"送你 🧠
✅ 响应式陷阱 & 解法对照表
| 陷阱 | 后果 | 正确方案 |
|---|---|---|
| reactive 解构 | UI 不更新 | toRef / toRefs |
| 直接替换 reactive | 响应断裂 | 用 ref |
| 数组索引赋值 | 偶发不更新 | splice / 替换 |
| deep watch 滥用 | 性能下降 | watch 精确字段 |
| watchEffect 乱用 | 依赖混乱 | 明确 watch |
| 静态数据 reactive | 无效依赖 | 普通对象 |
| 大对象深响应 | 初始化慢 | shallowRef |
| 第三方实例响应 | 内存浪费 | markRaw |
六、一句"老项目救命"的总结 😄
Vue 的响应式从来不是"写了就灵",
而是:
你要清楚地告诉它------哪些数据值得被监听,哪些不值得。
所以我最后送你一句很真实的反问:
你现在遇到的"Vue 不更新",真的是 Vue 的问题,还是你让它"监听得太多、管得太乱"? 😏