一、前言
005 篇埋过一个伏笔:“解构丢失响应式,要用 toRefs,那是 012 篇的主角”。今天它来了。这是响应式基础篇的收官前最后一课:搞懂它,前面所有"丢响应式"的坑一次性填平,也为第五阶段的组合式函数铺好路。
三秒回顾问题:解构 = 断链
constuser=reactive({name:'小明',age:18})const{name}=user// ❌ 拿到的是死值 '小明'name='小红'// 页面纹丝不动,Vue 也不认识这个变量了解构的一瞬间,值被"复印"了一份出来,和原对象的响应式链接当场断裂。toRef 系列的作用就一句话:解构,但不断链。
二、toRef:给单个属性"办一张连卡"
toRef(对象, '属性名')为对象的某个属性创建一个保持链接的 ref:
<template> <p>{{ name }}</p> <button @click="name = '小红'">改名</button> </template> <script setup> import { reactive, toRef } from 'vue' const user = reactive({ name: '小明', age: 18 }) // name 和 user.name 是同一个东西的两扇门 const name = toRef(user, 'name') // 改 name.value,user.name 跟着变,页面也更新 name.value = '小红' console.log(user.name) // '小红' </script>双向互通:改name.value→user.name变;改user.name→name.value也变。不是复印,是同一条线的两个插头。
三、toRefs:全部属性一起"办卡"
toRefs(对象)把对象第一层的每个属性都转成 toRef,返回一个普通对象。它就是为解构而生的:
<template> <p>{{ name }}:{{ age }} 岁</p> <button @click="age++">过生日</button> </template> <script setup> import { reactive, toRefs } from 'vue' const user = reactive({ name: '小明', age: 18 }) // ✅ 先 toRefs 再解构,每个变量都是活的 ref const { name, age } = toRefs(user) </script>解构出的 name、age 都是 ref,改动会同步回 user,页面照常更新。
script 里记得 .value,模板里不用——老规矩再次生效:
age.value++// ✅ script 里// 模板里直接 {{ age }}四、黄金搭档:reactive 的 state 转出去
组合式函数(第五阶段主角)的标准套路:内部用 reactive 聚合状态,对外返回时 toRefs 打包,调用方就能安心解构:
// useUser.js —— 一个迷你组合式函数import{reactive,toRefs}from'vue'exportfunctionuseUser(){conststate=reactive({name:'小明',age:18})functiongrow(){state.age++}// ❌ return state → 调用方一解构就断// ✅ return toRefs(state) → 怎么解构都是活的return{...toRefs(state),grow}}<template> <p>{{ name }}:{{ age }} 岁</p> <button @click="grow">长一岁</button> </template> <script setup> import { useUser } from './useUser' const { name, age, grow } = useUser() // ✅ 解构,响应式完好 </script>记住这个形态,第五阶段写 useMouse、useTitle 全是它。
五、props 的解构:同样的问题,同样的解法
props 也是响应式对象(代理),解构一样断链:
<template> <p>{{ msg }}</p> </template> <script setup> import { toRefs } from 'vue' const props = defineProps({ msg: String }) // ❌ const { msg } = props → 父组件更新后,页面不跟着变 // ✅ 正确姿势一:不解构,全程 props.xxx // ✅ 正确姿势二:toRefs 再解构 const { msg } = toRefs(props) </script>面试和实战都常考:props 为什么不能解构?答:props 是浅层响应式代理,解构取出的是当时的死值,断开了与父组件数据的链接;需要解构时用 toRefs 保持链接。
【截图位置:父组件改数据,子组件用 toRefs 解构后页面同步更新的演示】
六、踩坑记录
- toRefs 只转第一层:嵌套对象内部还是 reactive,
toRefs(user).info.value.city用起来别扭,深层就直接user.info.city访问 - toRef 一个不存在的属性也合法:
toRef(user, 'hobby')创建后hobby.value = '篮球'会写回 user,适合属性动态添加的场景 - toRefs 后忘了 .value:script 里
age++无效,要age.value++;模板里才自动解包 - 不要对 ref 用 toRefs:toRefs 是给 reactive 对象用的,ref 请直接用,别套娃
- 解构 computed 的返回值:computed 返回的是 ref,解构不丢响应式(ref 本身就是"盒子"),但
const x = computed(...).value就变死值了
七、今日小结
- toRef / toRefs:解构但不断链,让解构出的每个变量保持与原对象的连接
- 黄金搭档:内部 reactive 聚合 +
return toRefs(state)对外暴露 - props 解构必须 toRefs,或干脆全程 props.xxx
- 这套姿势是组合式函数的地基,第五阶段天天见
作者:陈志宝
下篇预告
响应式的"面"讲完了,最后挖一次"底":Vue2 的 $set 为什么没了?Vue3 改个新增属性为什么能自动更新?下一篇响应式原理通俗版——Proxy vs Object.defineProperty,面试高频题一次讲透。