2、ref与reactive:响应式数据的核心
好,咱们进入正题。这一章要聊的,是 Vue 3 组合式 API 里最基础、也最绕不开的两个家伙——ref 和 reactive。
说实话,我刚从 Options API 切换到组合式 API 时,第一个困惑就是:为什么要有两个响应式 API? 一个 ref 一个 reactive,到底该用哪个?
别急,咱们一步步拆开看。
2.1 ref:基本类型的响应式救星
ref 是用来包裹基本类型的。比如字符串、数字、布尔值这些。
你想想看,在 JavaScript 里,基本类型是按值传递的。Vue 没法直接追踪一个 let count = 0 的变化。所以 ref 就出场了——它把基本类型包装成一个对象,内部通过 .value 来访问。
核心要点:ref 返回的是一个响应式对象,访问和修改都需要通过 .value。
import { ref } from 'vue'
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
嗯,这里要注意:在模板里你不需要写 .value。Vue 会自动解包。但在 JavaScript 逻辑里,必须写。
个人经验:我刚开始写的时候,经常在 setup 函数里忘了加 .value,结果页面死活不更新。后来养成了一个习惯——只要在 script 里操作 ref,先看一眼有没有 .value。
2.2 reactive:对象的专属响应式
reactive 呢,是专门用来处理对象类型的。数组、对象、Map、Set 这些。
它直接返回一个响应式代理,不需要 .value。你直接操作属性就行。
import { reactive } from 'vue'
const user = reactive({
name: '张三',
age: 25
})
user.name = '李四' // 直接修改,不需要 .value
user.age++
是不是感觉更自然?就像操作普通对象一样。
我曾经踩过一个坑:用 reactive 包裹基本类型。比如 const count = reactive(0)。结果发现 count 根本不会响应。为什么?因为 reactive 只能处理对象,基本类型传进去就失效了。记住:reactive 只认对象。
2.3 两者的核心区别
咱们用一张表来对比,一目了然:
| 对比维度 | ref | reactive |
|---|---|---|
| 适用类型 | 基本类型(也可用于对象) | 对象类型(对象、数组等) |
| 访问方式 | 需要 .value |
直接访问属性 |
| 模板解包 | 自动解包,不需要 .value |
直接使用 |
| 重新赋值 | 支持整体替换(ref.value = newVal) |
不支持整体替换,会丢失响应式 |
| 解构 | 解构后仍保持响应式 | 解构后会丢失响应式 |
这里有个关键点我想强调一下:ref 其实也能包裹对象。当你用 ref 包裹一个对象时,Vue 内部会自动调用 reactive 来处理这个对象。所以 ref({ name: '张三' }) 是完全可以的。
但反过来不行——reactive 不能处理基本类型。
2.4 使用场景:什么时候该用谁?
我个人习惯是这样的:
- 基本类型:无脑用
ref。比如计数器、开关状态、输入框的值。 - 复杂对象:用
reactive。比如表单数据、用户信息、配置对象。 - 需要整体替换的场景:用
ref。比如从接口获取新数据后整体赋值。 - 需要解构的场景:用
ref。因为reactive解构后就不响应了。
避坑指南:我曾经在项目中用 reactive 包裹了一个数组,然后直接 arr = newArr 整体替换。结果页面没更新。为什么?因为 reactive 不支持整体重新赋值。正确的做法是用 arr.length = 0; arr.push(...newArr),或者干脆用 ref。
2.5 实战对比:同一个功能,两种写法
咱们来看一个实际的例子。假设我们要做一个用户信息编辑功能:
用 reactive 的写法:
const userForm = reactive({
name: '',
age: 0,
email: ''
})
function updateName(newName) {
userForm.name = newName // 直接修改
}
function resetForm() {
// 注意:不能直接 userForm = { name: '', age: 0, email: '' }
userForm.name = ''
userForm.age = 0
userForm.email = ''
}
用 ref 的写法:
const userForm = ref({
name: '',
age: 0,
email: ''
})
function updateName(newName) {
userForm.value.name = newName // 需要 .value
}
function resetForm() {
userForm.value = { name: '', age: 0, email: '' } // 整体替换,没问题
}
你看,ref 在需要整体重置时特别方便。而 reactive 在频繁修改单个属性时更简洁。
2.6 我的建议
说实话,没有绝对的「谁更好」。我见过很多团队统一用 ref,也见过混合使用的。我个人更倾向于:
- 简单场景:全部用
ref。统一写法,减少心智负担。 - 复杂表单或深层嵌套对象:用
reactive。操作起来更自然,不用到处写.value。
但不管你怎么选,记住一点:一旦选定了,就保持一致。别在一个组件里混用太多,不然维护起来真的很痛苦。
小技巧:如果你不确定该用哪个,先问自己一个问题——「我之后会不会整体替换这个数据?」如果会,用 ref;如果只是修改属性,用 reactive。
好了,这一章的内容就到这里。下一章咱们聊聊 computed 和 watch,这两个家伙在实战中也是高频选手。到时候我会分享一些我在项目中用它们「救火」的经历,挺有意思的。