人人都会AI编程

基本类型用 ref、引用类型用 reactive 的选型

更新时间:2026-07-10

Vue 3 提供了两种定义响应式数据的主要方式:refreactive。很多开发者一开始容易陷入“该用哪个”的纠结里,其实选型规则可以归纳为一句简单的话:基本类型用 ref,引用类型随意,但推荐对象也走 ref 统一的模式。下面展开说明为什么这样选,以及需要避开的坑。

为什么基本类型必须用 ref

reactive 是基于 Proxy 实现的,而 Proxy 只能代理对象类型的数据。如果你把一个基本类型(如字符串、数字、布尔值)直接传给 reactive,它要么报错,要么无法保持响应式。因此,对于数字、字符串这类单个值,唯一的选择就是 ref

import { ref } from 'vue'

const count = ref(0)      // 正确:基本类型用 ref
const name = ref('Tom')   // 正确
let countReactive
try {
  countReactive = reactive(100) // 报错或无效
} catch (e) {
  console.log('reactive 不能接收基本类型')
}

对象类型用 reactive 的“天然直觉”

reactive 是为对象设计的 API,它返回一个代理对象,你直接操作属性的增加、修改、删除都能自动触发响应式更新,而且使用时不需要 .value,写起来更接近原生对象的习惯。

import { reactive } from 'vue'

const user = reactive({
  name: 'Alice',
  age: 30
})

user.age = 31          // 视图自动更新,无需 user.value.age
user.department = 'FE' // 新增属性也支持响应式(Vue 3 的 Proxy 优势)

这种“不用写 .value”的体验,让很多开发者在处理复杂对象时首选 reactive

为什么实际开发中 ref 更“稳”

尽管 reactive 看起来很自然,但它有几个硬伤,导致大型项目和社区最佳实践中反而更推荐统一使用 ref,即使对于对象类型。

1. 解构或替换整个对象会丢失响应式
reactive 返回的响应式状态与原始对象本身绑定在一起。如果你对它进行解构、展开,或者直接重新赋值,响应式就断了。

const state = reactive({ count: 1, message: 'hello' })

// 解构丢失响应式:
const { count, message } = state
count++  // 视图不更新!

// 直接替换整个对象丢失响应式:
state = reactive({ count: 2 }) // 不行,而且这是重新赋值变量,通常不会这样写
// 正确做法:用 Object.assign 覆盖属性
Object.assign(state, { count: 2 })

而在 ref 中,响应式连接在 .value 上,你可以随意对 .value 赋值新的对象,响应式依然保持:

const state = ref({ count: 1, message: 'hello' })

state.value = { count: 2, message: 'world' } // 视图更新
const { count, message } = state.value       // 解构的是普通值,不指望它保持响应式

2. reactive 不能直接替换整个值
当你把一个 API 返回的新数据直接赋予 reactive 变量时,你不能简单写 myData = newData,因为这会让 myData 指向一个新对象,失去响应式。你只能遍历赋值或 Object.assign。而 ref 只需要 myData.value = newData

3. 组合式函数(Hooks)返回值的统一性
如果你写一个自定义 Hook,返回的是 reactive 对象,外部解构使用会失去响应式,必须要求外部了解且避免解构;返回的是 ref,则 .value 的写法只是调用侧的规则,但不会因为不当操作丢失响应式。因此,为了 Hook 的健壮性,官方推荐在 Hook 中暴露 ref

两种“统一选型”策略

社区经过大量实践后,形成两种主流选择策略,你可以任选其一并在团队内保持一致:

策略 A:纯 ref 流(通用推荐)

  • 所有响应式数据都用 ref,包括对象和数组。
  • .value 写起来稍微啰嗦,但语义统一,不会遇到解构丢失响应式或替换整个值的问题。
  • <script setup> 中可以直接用 const count = ref(0),模板里会自动解包(不用写 .value),所以实际上补充的成本只在 JS 逻辑代码中。

策略 B:分类型选用流

  • 基本类型用 ref,对象/数组用 reactive
  • 要遵守“不直接对 reactive 对象解构、不直接重新赋值”的纪律。
  • 需要使用 toRefs() 来安全解构,增加一些模板代码。

关键点速记

| 场景 | 推荐 | 原因 |
|------|------|------|
| 数字、字符串、布尔值 | ref | reactive 不支持基本类型 |
| 单个数组或对象,且不会解构 | refreactive 均可 | ref 更灵活,reactive 少写 .value |
| 需要安全解构或整体替换 | ref | .value 赋值天然响应,避免丢失 |
| 自定义 Hook 返回 | ref | 保证外部使用安全 |
| 表单、配置等局部状态 | reactive 也可以 | 作用域小,不会到处解构,直观 |

一句话总结:Vue 官方提供两种 API 是为了灵活,而不是制造困扰。最简单的选择就是所有响应式数据都用 ref,这几乎可以避免所有关于响应式丢失的坑,是整个团队最容易统一、代码审查成本最低的方案。