1. 引言
Vue 3 的 Composition API 带来了全新的响应式系统,其中 ref() 和 reactive() 是两个最基础、最核心的 API。它们是构建响应式数据状态的基石,理解它们的区别、适用场景以及底层原理,对于写出高效、可维护的 Vue 3 应用至关重要。
本文将深入剖析 ref() 与 reactive() 的用法、核心区别、底层实现原理,并通过大量实战示例帮助你彻底掌握它们。
2. 为什么需要 ref() 和 reactive()?
在 Vue 2 中,我们习惯在 data() 选项中定义响应式数据。Vue 3 的 Composition API 将逻辑组织方式从「选项」转变为「函数」,我们需要一种在 <script setup> 或 setup() 函数中独立声明响应式状态的方式。
ref() 和 reactive() 正是为此而生。它们都用于创建响应式数据,但设计哲学和使用场景有所不同。
3. reactive():对象响应式的利器
3.1 基本用法
reactive() 接收一个普通对象(或数组、Map、Set 等),返回一个响应式代理对象。它基于 Proxy 实现,能够深度侦测对象内部所有属性的变化。
javascript
import { reactive } from 'vue'
const state = reactive({
count: 0,
user: {
name: '张三',
age: 25
}
})
// 修改属性,视图会自动更新
state.count++
state.user.name = '李四'
3.2 特点与限制
- 深度响应式:嵌套对象也会被自动转换为响应式。
- 仅支持对象类型:不能用于原始类型(string、number、boolean 等)。
- 解构会丢失响应性:这是它最大的痛点。
javascript
const state = reactive({ count: 0 })
// 解构后 count 不再是响应式的
const { count } = state
count++ // 视图不会更新
4. ref():全能型响应式 API
4.1 基本用法
ref() 可以接收任何类型的值(包括原始类型和对象),返回一个带有 .value 属性的响应式引用。
javascript
import { ref } from 'vue'
const count = ref(0)
const name = ref('张三')
const user = ref({ name: '李四', age: 30 })
// 修改值
count.value++
name.value = '王五'
user.value.name = '赵六'
4.2 模板中的自动解包
在模板中使用时,ref 会自动解包,无需写 .value:
vue
<template>
<p>{{ count }}</p>
<button @click="count++">增加</button>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
4.3 ref 包裹对象时的行为
当 ref() 接收一个对象时,内部会调用 reactive() 将其转换为响应式,因此 ref 也具备深度响应式能力。
javascript
const user = ref({ name: '张三', age: 25 })
// 深层属性也是响应式的
user.value.age = 26 // 视图更新
5. ref() 与 reactive() 的核心区别
| 对比维度 | ref() |
reactive() |
|---|---|---|
| 支持的数据类型 | 任意类型(原始类型 + 对象) | 仅对象类型(Object、Array、Map、Set) |
| 访问方式 | 通过 .value 访问 |
直接访问属性 |
| 模板中访问 | 自动解包,无需 .value |
直接访问 |
| 解构响应性 | 解构后仍保持响应性 | 解构后丢失响应性 |
| 底层实现 | 原始类型用 Object.defineProperty,对象内部用 reactive |
基于 Proxy |
| 适用场景 | 通用,尤其适合单个值、组合式函数返回值 | 适合集中管理复杂对象状态 |
6. 实战场景选择指南
6.1 优先使用 ref() 的场景
- 定义单个原始类型变量(计数器、开关状态、输入框值等)。
- 在组合式函数(Composables)中返回响应式状态。
- 需要解构或重新赋值整个数据源。
javascript
// 组合式函数示例
export function useCounter() {
const count = ref(0)
const increment = () => count.value++
return { count, increment }
}
6.2 优先使用 reactive() 的场景
- 集中管理一组相关的状态(如表单数据、页面配置)。
- 深层嵌套的对象结构,且不需要整体替换。
javascript
const form = reactive({
username: '',
password: '',
remember: false
})
6.3 解构 reactive 的正确姿势
如果必须解构 reactive 对象并保持响应性,可以使用 toRefs() 或 toRef():
javascript
import { reactive, toRefs } from 'vue'
const state = reactive({ count: 0, name: '张三' })
// 使用 toRefs 解构,保持响应性
const { count, name } = toRefs(state)
// 此时 count 和 name 是 ref,需要 .value 访问
count.value++
7. 底层原理浅析
7.1 reactive 的 Proxy 实现
reactive() 使用 ES6 的 Proxy 拦截对象的读取、写入、删除等操作,在 get 时收集依赖,在 set 时触发更新。
javascript
const handler = {
get(target, key, receiver) {
// 收集依赖
track(target, key)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
// 触发更新
trigger(target, key)
return result
}
}
7.2 ref 的实现
对于原始类型,ref 通过 Object.defineProperty 拦截 .value 的读写;对于对象类型,则内部调用 reactive() 处理。
javascript
class RefImpl {
constructor(value) {
this._value = toReactive(value)
}
get value() {
track(this, 'value')
return this._value
}
set value(newVal) {
this._value = toReactive(newVal)
trigger(this, 'value')
}
}
8. 常见陷阱与最佳实践
8.1 不要用 reactive 包裹 ref
javascript
// 错误示例
const count = ref(0)
const state = reactive({ count })
// 正确做法
const state = reactive({ count: 0 })
8.2 避免直接替换 reactive 对象
javascript
const state = reactive({ count: 0 })
// 错误:直接替换会丢失响应性
state = reactive({ count: 1 })
// 正确:使用 ref 或修改属性
state.count = 1
8.3 在模板中注意 ref 的解包规则
只有顶层 ref 属性才会在模板中自动解包,嵌套在响应式对象中的 ref 不会自动解包。
vue
<template>
<!-- 顶层 ref 自动解包 -->
<p>{{ count }}</p>
<!-- 嵌套在对象中的 ref 不会自动解包 -->
<p>{{ obj.count }}</p> <!-- 显示 0,而不是 0 的 value -->
</template>
<script setup>
import { ref, reactive } from 'vue'
const count = ref(0)
const obj = reactive({ count: ref(0) })
</script>
9. 总结
ref()是全能型 API,适合所有场景,尤其是单个值和组合式函数。reactive()适合集中管理复杂对象状态,但要注意解构丢失响应性的问题。- 在
<script setup>中,ref在模板中自动解包,体验更佳。 - 理解底层
Proxy和依赖收集机制,有助于写出更高效的代码。
在实际开发中,建议以 ref() 为主,reactive() 为辅,根据具体场景灵活选择。掌握这两者的精髓,你的 Vue 3 开发效率将大幅提升。