4.2 Vue3中reactive与ref详解及区别
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 } = statecount++ // 不会触发视图更新// ✅ 正确:使用 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),无需
.value
。
<!-- 模板中 --> <template><div>{{ count }}</div> <!-- 自动解包,显示 count.value --><div>{{ user.age }}</div> <!-- 自动解包,显示 user.value.age --><button @click="count++">+</button> <!-- 模板中也无需 .value --> </template>
- 在 JavaScript 中读取或修改值时,必须使用
- 解包 (Unwrapping):
- 当
ref
被作为属性添加到reactive
对象中时,会自动解包。- 在
ref
内部,如果值是对象,也会自动转换为reactive
。
- 在
const count = ref(0) const state = reactive({count, // 自动解包,state.count 等同于 count.valuename: '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
因其通用性而被广泛使用。