同样是创建响应式数据,ref 和 reactive 到底什么时候用?为什么 reactive 解构之后就丢失响应式了?toRef、toRefs 又是用来解决什么问题的?
这一组 API 是 Vue3 日常开发里最高频、也最容易踩坑的地方,今天一次性梳理清楚,搭配示例,看完直接可以用到项目里。一、先简单回顾:ref 和 reactive 基础用法
1. ref
ref() 既可以包裹基础类型(字符串、数字、布尔、null、undefined),也可以包裹对象、数组。它底层会把数据包装成一个 Ref 对象,内部值放在 .value 属性上。<scriptsetup>import { ref } from 'vue'// 基础类型const count = ref(0)// 对象类型const user = ref({ name: '张三', age: 20 })// 读取、修改必须加 .valueconsole.log(count.value)count.value = 10user.value.name = '李四'</script><template> <!-- 模板中自动解包,不用写 .value --> <div>{{ count }}</div> <div>{{ user.name }}</div></template>
2. reactive
reactive()只支持对象、数组这类引用类型,不支持基础类型。它内部就是用我们之前讲的 Proxy 做代理,返回原始对象的响应式代理,没有 .value。<scriptsetup>import { reactive } from 'vue'const user = reactive({ name: '张三', age: 20 })const list = reactive([1,2,3])// 直接访问,不需要 .valueuser.name = '李四'</script><template> <div>{{ user.name }}</div></template>
重点提醒:如果你直接给 reactive 变量整体重新赋值,响应式会直接丢失!二、ref 和 reactive 怎么选?一张表看懂
| | |
| | |
| | |
| | |
| 基础类型靠类包装;对象内部自动调用 reactive | |
| | |
- 表单、多个属性组成的对象 → 优先 reactive
三、致命问题:reactive 解构丢失响应式
<scriptsetup>import { reactive } from 'vue'const user = reactive({ name: '张三', age: 20 })// 解构之后,name、age 变成普通变量,不再响应式const { name, age } = user</script><template> <div>{{ name }}</div></template>
为什么解构之后就不响应了? 因为 reactive 返回的是 Proxy 代理对象,解构相当于把对象里的原始值拷贝出来,拿到的是普通值,脱离了 Proxy 的代理追踪。四、toRefs:把 reactive 对象批量转成 ref
toRefs接收一个 reactive 响应式对象,把对象的每一个属性都转为 ref,之后再解构,保留响应式。<scriptsetup>import { reactive, toRefs } from 'vue'const user = reactive({ name: '张三', age: 20 })// 批量转为 refconst { name, age } = toRefs(user)// js 里修改需要 .valuename.value = '李四'</script><template> <div>{{ name }}</div></template>
- 把对象属性传递给子组件、composable 函数返回
注意:toRefs 只会处理对象上已存在的属性,新增属性不会自动变成 ref。五、toRef:单独给某一个属性创建 ref
toRef和 toRefs 不一样,它是针对单个属性创建 ref,而不是批量转换。 适合:只想抽离对象其中一个属性,而不是全部。import { reactive, toRef } from 'vue'const user = reactive({ name: '张三', age: 20 })// 单独取出 nameconst nameRef = toRef(user, 'name')nameRef.value = '李四'
六、补充:浅层响应式 shallowRef /shallowReactive
很多业务场景不需要深度监听,比如后端返回超大列表、大对象,深度监听会有性能损耗,这时用浅层 API:- shallowRef:只监听.value的替换,不会对内部对象做响应式
- shallowReactive:只监听对象第一层属性,深层属性不做响应式
import { shallowRef } from 'vue'const bigData = shallowRef({ list: [...] })// 有效:替换 value,触发更新bigData.value = { list: [] }// 无效:修改深层属性,不会触发视图更新bigData.value.list[0].name = 'xxx'
七、高频坑点汇总
- reactive 不能存基础类型,会报错,基础类型请用 ref
- reactive 直接解构会丢失响应式,解构记得套 toRefs
- toRef /toRefs 创建的 ref,和原对象是引用关系,修改会同步影响原对象。
- toRefs 不能用于普通原始对象,普通对象转出来的 ref 不是响应式。
八、总结
- 基础类型 → ref;结构化对象表单 → reactive
- reactive 解构丢失响应式,toRefs 是标准解决方案
- toRef 取单个属性,toRefs 批量转换全部属性
- 超大对象、列表考虑 shallowRef /shallowReactive 优化性能
弄懂这一组,你在写<script setup> 的时候,绝大多数响应式报错都能自己定位了。
下一篇预告:Vue3 组件通信全梳理,props /emit/defineModel /provide/defineExpose 对比讲解,组件传值不再混乱。如果你觉得文章有帮助,欢迎点赞、在看,一起打卡 Vue3 系列学习✨