Vue3 中 reactive 嵌套 ref 会自动解包顶层属性,读写均作用于 .value;但不递归穿透深层嵌套、数组或 Map 中的 ref;需保持 ref 形态时应避免放入 reactive 或使用 shallowRef。

在 reactive 中嵌套 ref 是完全可行的,而且 Vue3 会自动对 ref 进行“解包”(unwrapping)——也就是你访问时直接拿到 ref.value 的值,无需写 .value。这不是 bug,是设计好的行为,但容易被误解为“响应性丢失”或“脱靶”。关键在于理解它何时生效、何时不生效。
reactive 里放 ref 会自动解包
当你把一个 ref 对象作为属性值放进 reactive 对象中,Vue3 在响应式代理中会对该 ref 做特殊处理:读取时自动返回其 .value,写入时也自动同步到 .value。
- 例如:const count = ref(10); const state = reactive({ count }); console.log(state.count); // 输出 10(不是 ref 对象)
- 修改也有效:state.count = 20; console.log(count.value); // 输出 20
- 模板中同样省略 .value:{{ state.count }} 渲染的是 20
只对顶层 ref 属性解包,不递归穿透
这个自动解包只发生在 reactive 对象的**直接属性**上,不会深入嵌套结构。
Vue 3 组合式 API 与 最佳实践,涵盖代码组织、useXxx 模式、组合式函数设计、Store 集成、响应式优化及功能抽取。
- ✅ 有效(顶层属性):reactive({ count: ref(5) }).count → 自动解包为 5
- ❌ 无效(深层嵌套):reactive({ user: { age: ref(25) } }).user.age → 仍是 ref 对象,不会解包
- ⚠️ 数组/Map 中的 ref 不解包:reactive({ list: [ref(1), ref(2)] }).list[0] → 返回 ref(1),不是 1
什么时候需要手动保留 ref?
如果你希望某个字段始终是 ref 类型(比如要传给子组件的 defineModel、或需调用 .value 的逻辑),就别依赖自动解包,而是显式使用 shallowRef 或避免放进 reactive。
立即学习“前端免费学习笔记(深入)”;
- 推荐做法:把需要保持 ref 形态的变量留在 setup 顶层,不要塞进 reactive 对象里
- 替代方案:const countRef = shallowRef(0); const state = reactive({ ... }); // countRef 独立管理
- 如果已嵌套且需访问原始 ref,可用 toRaw(state).count 拿到底层 ref 实例(但一般不建议)
和 toRefs 的区别别混淆
toRefs 是把 reactive 对象的每个属性转成独立的 ref,用于解构后仍保持响应式;而 reactive 内部的 ref 解包是读取时的语法糖,两者目的不同、机制不同。
-
toRefs(state) → 返回 { count: Ref
} ,解构后仍可响应式更新 - state.count(内含 ref)→ 访问时自动解包,但 state.count 本身不是 ref 类型,不能解构赋值再响应

















