响应式对象解构后失效,根本原因是解构得到的是值的副本而非响应式代理引用;toRefs 通过将每个属性转为独立 ref 维持拦截能力,更新需用 .value。

响应式对象解构后失效,根本原因在于:解构操作拿到的是值的副本,而不是对响应式代理的引用。Vue 的响应式能力依赖 Proxy 或 ref 包装器持续拦截 get/set,一旦脱离这个包装链,更新就无法被系统捕获。
为什么解构会切断响应式链
Vue 3 的 reactive 返回一个 Proxy 对象,所有属性访问都必须通过它触发依赖收集;ref 则返回一个带 .value 的包装对象,内部靠 getter/setter 实现追踪。
- 写
const { name } = state→ 实际是const name = state.name,取出来的是原始字符串或数字,不再经过 Proxy 的 get 拦截 - 写
const { name } = user.value→ 相当于直接读取普通对象属性,ref 的响应式外壳已被剥离 - 后续对
name的赋值(如name = '李四')只是改本地变量,不会触发 set,也不会通知视图更新
toRefs 是怎么解决问题的
toRefs 不是简单“转成 ref”,而是把响应式对象的每个属性,单独映射为一个独立的 ref。这样每个解构出来的变量仍是响应式引用,修改时需通过 .value 赋值,从而维持拦截能力。
- 输入:
reactive({ count: 0, msg: 'hi' }) - 经
toRefs()后变成:{ count: ref(0), msg: ref('hi') } - 解构后:
const { count, msg } = toRefs(state),此时count和msg都是 ref,不是原始值 - 更新写法:
count.value++或msg.value = 'new',能正常触发响应
实际使用中的关键注意点
toRefs 有效,但用错地方依然会失效。这几个细节容易被忽略:
-
只对 reactive 对象调用 toRefs:不能对
ref().value再套 toRefs,应先用unref或直接用toRef处理单个属性 -
模板中仍要写 .value(在 script setup 中可省略):若在 Options API 或非 setup 脚本里使用,绑定时得写
{{ count.value }} -
嵌套对象需递归处理:toRefs 只做一层扁平化,如果
state.user是个 reactive 对象,需额外对user再调一次toRefs或用toRef -
避免解构后再赋新对象:比如
count = ref(99),会覆盖原来的 ref 引用,响应式中断
替代方案对比:什么时候该用哪个
除了 toRefs,还有几个常用方式,适用场景不同:
-
直接返回整个 reactive 对象:适合属性多、模板中频繁访问的场景,模板里写
state.count即可,无需 .value -
用 toRef 提取单个属性:比如只关心
state.id,想把它传给子组件或 computed,写toRef(state, 'id')更轻量 -
组合式函数中统一暴露:常见于自定义 Hook,用
return { ...toRefs(data) }让调用方解构无忧 - ref + 解构 + .value 更新:不推荐,易出错;正确做法是保持 ref 包装,避免解构原始值

















