reactive
和 ref
是 Vue 3 Composition API 中创建响应式数据的两个核心函数。它们都基于 Proxy
实现了数据的响应式,但使用场景和方式有所不同。
一、reactive
1. 基本概念
- 作用 :将一个对象(或数组)转换为响应式对象。
- 原理 :使用
Proxy
对传入的对象进行深度代理,拦截其所有属性的读取(get
)和设置(set
)操作。 - 返回值 :返回一个代理对象(Proxy),该对象是原始对象的响应式副本。
2. 基本用法
import { reactive } from 'vue'
// 响应式对象
const state = reactive({
count: 0,
name: 'Vue',
user: {
age: 25
},
list: [1, 2, 3]
})
// 直接修改属性
state.count++
state.name = 'Composition API'
state.user.age = 30
state.list.push(4)
// 在模板中使用
// <template>
// <div>{{ state.count }}</div>
// <div>{{ state.user.age }}</div>
// </template>
3. 特点
- 只适用于对象/数组 :不能用于基本类型(
string
,number
,boolean
,null
,undefined
,symbol
)。 - 深层响应式:对嵌套对象和数组也是响应式的。
- 直接访问 :在 JavaScript 和模板中都直接通过
.
或[]
访问属性,无需.value
。 - 代理对象 :返回的是一个
Proxy
对象,与原始对象不相等 (state !== originalObject
)。
4. 注意事项
-
解构会失去响应性 :
const state = reactive({ count: 0, name: 'Alice' }) // ❌ 错误:解构后 count 和 name 是普通变量,失去响应性 const { count, name } = state count++ // 不会触发视图更新 // ✅ 正确:使用 toRefs const { count, name } = toRefs(state) // 此时 count 和 name 是 ref,需要 .value count.value++ // ✅ 会触发更新
-
替换整个对象会失去响应性 :
const state = reactive({ count: 0 }) // ❌ 错误:直接赋值一个新对象,会丢失响应性连接 state = { count: 1 } // state 不再是响应式的 // ✅ 正确:修改对象属性 state.count = 1 // ✅ 正确:如果需要替换,可以重新 reactive Object.assign(state, { count: 1, name: 'Bob' })
-
Set/Map/WeakSet/WeakMap :
reactive
也可以代理这些集合类型。
二、ref
1. 基本概念
- 作用 :创建一个响应式引用。它可以包装任何类型的值(基本类型或对象)。
- 原理 :
- 对于基本类型 :创建一个包含
.value
属性的对象,并使用Object.defineProperty
(Vue 2) 或Proxy
(Vue 3) 使其响应式。 - 对于对象类型 :内部会自动调用
reactive()
进行转换。
- 对于基本类型 :创建一个包含
- 返回值 :返回一个包含
.value
属性的对象。
2. 基本用法
import { ref } from 'vue'
// 响应式基本类型
const count = ref(0)
const name = ref('Vue')
const isActive = ref(true)
// 修改值
count.value++ // 必须使用 .value
name.value = 'Composition API'
// 响应式对象 (内部调用 reactive)
const user = ref({
age: 25
})
user.value.age = 30 // 注意:user 是 ref,user.value 是 reactive 对象
// 响应式数组
const list = ref([1, 2, 3])
list.value.push(4)
3. 特点
-
通用性强 :可以用于任何类型的数据。
-
.value
访问 :-
在 JavaScript 中读取或修改值时,必须 使用
.value
。 -
在 模板 (template) 中使用时,Vue 会自动解包(unwrapping),无需
<template>.value
。{{ count }}{{ user.age }}<button @click="count++">+</button> </template>
-
-
解包 (Unwrapping) :
- 当
ref
被作为属性添加到reactive
对象中时,会自动解包。-
在
ref
内部,如果值是对象,也会自动转换为reactive
。const count = ref(0)
const state = reactive({
count, // 自动解包,state.count 等同于 count.value
name: 'Alice'
})console.log(state.count) // 0 (直接访问,无需 .value)
state.count++ // 相当于 count.value++
console.log(count.value) // 1
-
- 当
4. 注意事项
-
JavaScript 中必须用
.value
:忘记.value
是常见错误。 -
模板中自动解包:这是 Vue 的优化,让模板更简洁。
-
数组索引 :
ref
包装的数组,在 JavaScript 中访问元素仍需.value
。const list = ref([1, 2, 3]) console.log(list.value[0]) // ✅ 正确 // console.log(list[0]) // ❌ 错误,list 是 ref 对象,不是数组
三、reactive
与 ref
的核心区别
特性 | reactive() |
ref() |
---|---|---|
适用类型 | 仅对象/数组 (Object, Array, Map, Set 等) | 任何类型 (基本类型 + 对象/数组) |
返回值 | 响应式代理对象 (Proxy) | 包含 .value 的响应式对象 |
访问方式 (JS) | 直接访问属性 (obj.prop ) |
必须通过 .value 访问 (ref.value ) |
访问方式 (模板) | 直接访问 ({``{ obj.prop }} ) |
自动解包 ,直接访问 ({``{ ref }} ) |
解构 | 直接解构会失去响应性 (需 toRefs ) |
解构后仍是 ref ,需 .value |
替换 | 替换整个对象会失去响应性 | 可以安全地替换 ref.value |
性能 | 深层代理,可能稍重 | 基本类型轻量,对象内部用 reactive |
类型推断 (TS) | 类型保持不变 | 包装为 Ref<T> |
四、如何选择?
-
优先使用
ref
:- 当你不确定数据类型时。
- 当你需要一个基本类型 的响应式变量时(如
count
,show
,inputValue
)。 - 当你希望代码风格统一,减少
toRefs
的使用(尤其是在<script setup>
中)。 - 当你需要替换整个值 时(
ref.value = newValue
)。
-
使用
reactive
:- 当你有一个复杂的对象结构,并且希望直接操作其属性时。
- 当你希望代码在 JavaScript 中看起来更"自然"(无需
.value
)。 - 注意解构问题,必要时配合
toRefs
使用。
推荐实践
-
<script setup>
语法糖中 :很多开发者倾向于统一使用ref
,因为它更通用,且在模板中自动解包,JS 中虽然要.value
,但 IDE 通常能很好提示。这可以减少对toRefs
的依赖。 -
复杂状态对象 :如果有一个包含多个相关属性的大对象,使用
reactive
可能更直观,但记得用toRefs
解构。// 推荐:统一使用 ref (尤其在 script setup 中)
const count = ref(0)
const name = ref('')
const userList = ref([])// 或者:复杂对象用 reactive + toRefs
const formState = reactive({
name: '',
email: '',
age: 0
})
const { name, email, age } = toRefs(formState) // 解构后保持响应性
五、总结
reactive
是为对象量身定制的响应式解决方案,使用方便但有解构陷阱。ref
是一个通用 的响应式容器,通过.value
包装任何值,是处理基本类型和需要灵活替换场景的首选。- 两者可以结合使用,
ref
在reactive
对象中会被自动解包。 - 选择哪个主要取决于你的数据结构、编码习惯和对解包/解构的偏好。在现代 Vue 开发中,
ref
因其通用性而被广泛使用。