Vue 3的响应式系统是其最核心的特性之一,主要通过ref
和reactive
这两个API来实现。本文将详细介绍这两个API的使用方法、区别以及最佳实践。
1. ref()
的基本使用
ref()
用于创建一个响应式的数据引用。它可以包装任何类型的值,包括基本类型和对象类型。
1.1 基本类型示例
import { ref } from 'vue'// 创建一个ref
const count = ref(0)// 访问值
console.log(count.value) // 0// 修改值
count.value++
console.log(count.value) // 1// 在模板中使用(不需要.value)
<template><div>{{ count }}</div>
</template>
1.2 对象类型示例
import { ref } from 'vue'const user = ref({name: 'Alice',age: 25
})// 访问和修改对象属性
console.log(user.value.name) // 'Alice'
user.value.age = 26// 替换整个对象
user.value = {name: 'Bob',age: 30
}
2. reactive()
的基本使用
reactive()
用于创建一个响应式对象,但只能用于对象类型(包括数组和Map、Set等集合类型)。
reactive()
返回的是一个原始对象的 Proxy
,它和原始对象是不相等的:
const raw = {}
const proxy = reactive(raw)// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false
2.1 基本示例
import { reactive } from 'vue'const state = reactive({user: {name: 'Alice',age: 25},posts: []
})// 直接访问和修改属性(不需要.value)
console.log(state.user.name) // 'Alice'
state.user.age = 26
state.posts.push({ id: 1, title: 'Hello Vue 3' })
2.2 数组示例
const list = reactive([1, 2, 3])// 数组方法都会触发响应式更新
list.push(4)
list.pop()
list[0] = 10
3. ref vs reactive的主要区别
-
使用方式
- ref:需要通过
.value
访问和修改值(模板中例外) - reactive:直接访问和修改属性
- ref:需要通过
-
适用类型
ref
:可以包装任何类型reactive
:只能用于对象类型ref
返回一个由RefImpl
类构造出来的对象,而reactive
返回一个原始对象的响应式代理Proxy
。
-
嵌套转换:
// ref的嵌套对象转换
const user = ref({name: 'Alice',info: { age: 25 }
})
// 结构:{ value: Proxy({ name: 'Alice', info: Proxy({ age: 25 }) }) }// reactive的嵌套对象转换
const state = reactive({user: {name: 'Alice',info: { age: 25 }}
})
// 结构:Proxy({ user: { name: 'Alice', info: Proxy({ age: 25 }) } })
4. 注意事项
- 响应式丢失情况
import { reactive, toRefs } from 'vue';
const state = reactive({ count: 0 })// ❌ 错误用法:解构后失去响应性
const { count } = state// ✅ 正确用法:使用计算属性
const count = computed(() => state.count)// ✅ 正确用法:使用toRefs
const { count } = toRefs(state); // 此时 count 仍然为响应式
- ref的自动解包
const count = ref(0)
const state = reactive({count // 在reactive对象中会自动解包
})console.log(state.count) // 直接访问值,不需要.value
- 不能直接替换reactive对象
let state = reactive({ count: 0 })// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失!)
state = reactive({ count: 1 }) // 这将导致错误// ✅ 正确用法:修改属性值
state.count = 1//✅ 正确用法:使用 Object.assign 合并对象
Object.assign(state, { count: 1, otherProp: 'value' })//✅ 正确用法:如果存在替换整个对象的需求,可以考虑使用 ref 代替 reactive:
const state = ref({ count: 0 })
state.value = { count: 1 } // 合法操作
- 在模板中解包
//在模板渲染上下文中,只有顶级的 ref 属性才会被解包。
//在下面的例子中,count 和 object 是顶级属性,但 object.id 不是:
const count = ref(0)
const object = { id: ref(1) }<template>
{{ count + 1 }}
<!-- 但下面这个不会:-->
{{ object.id + 1 }}
</template>
//渲染的结果将是 [object Object]1,因为在计算表达式时 object.id 没有被解包,仍然是一个 ref 对象。为了解决这个问题,我们可以将 id 解构为一个顶级属性:
<template><!-- 在模板中自动解包 --><div>{{ count }}</div><!-- 在v-bind中也会自动解包 --><input :value="count"><!-- 但在事件处理器中需要.value --><button @click="count.value++">增加</button>
</template>
5. 总结
1. 核心特性对比
特性 | ref | reactive |
---|---|---|
数据类型 | 任何类型 | 仅对象类型 |
访问方式 | .value(模板中自动解包) | 直接访问 |
实现方式 | RefImpl类 | Proxy |
解构行为 | 保持响应性 | 失去响应性 |
对象处理 | 内部使用reactive | 直接代理 |
2. 最佳实践要点
-
类型选择:
- 基本类型使用
ref()
- 复杂对象使用
reactive()
- 官方建议使用
ref()
作为声明响应式状态的主要 API
- 基本类型使用
-
避免常见陷阱:
- 不要解构
reactive
对象 - 不要直接替换
reactive
对象 - 注意在事件处理器中使用
ref
需要.value
- 不要解构
-
性能优化:
- 合理使用
shallowRef
和shallowReactive
- 避免不必要的深层响应式转换
- 大型数据集合考虑使用
shallowRef
- 合理使用