2、ref与reactive:响应式数据的核心

好,咱们进入正题。这一章要聊的,是 Vue 3 组合式 API 里最基础、也最绕不开的两个家伙——refreactive

说实话,我刚从 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

好了,这一章的内容就到这里。下一章咱们聊聊 computedwatch,这两个家伙在实战中也是高频选手。到时候我会分享一些我在项目中用它们「救火」的经历,挺有意思的。