Vue ref 和 reactive
在 Vue 3 的组合式 API 中,ref 和 reactive 是两个最常用的响应式 API。它们是实现 Vue 响应式系统的基础工具。本章将详细介绍它们的用法、区别和最佳实践。
ref()
ref() 用于创建响应式数据,可以包装任意类型的值。
为什么需要 ref?
JavaScript 中没有办法拦截基本类型(如数字、字符串)的直接访问和修改。ref() 通过将值包装在一个对象中,利用 .value 属性的 getter 和 setter 来实现响应式追踪:
// 问题:基本类型的修改无法被拦截
let count = 0
count = 1 // 没有机制可以知道 count 被修改了
// 解决:包装成对象
const count = ref(0)
count.value = 1 // 可以在 setter 中拦截
基本用法
import { ref } from 'vue'
// 基本类型
const count = ref(0)
const name = ref('张三')
const isActive = ref(true)
// 读取值(需要 .value)
console.log(count.value) // 0
console.log(name.value) // '张三'
console.log(isActive.value) // true
// 修改值
count.value++
name.value = '李四'
isActive.value = false
ref 对象的结构
ref() 返回一个包含 .value 属性的对象:
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
// ref 对象的内部结构(简化)
// {
// __v_isRef: true, // 标记为 ref
// _rawValue: 0, // 原始值
// _value: 0, // 响应式值
// get value() { ... },
// set value(newValue) { ... }
// }
在模板中使用
在模板中使用时,Vue 会自动解包 ref,不需要 .value:
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++ // 在 JavaScript 中需要 .value
}
</script>
<template>
<!-- 自动解包,显示 0 -->
<p>{{ count }}</p>
<!-- 在表达式中也可以直接使用 -->
<p>{{ count + 1 }}</p>
<button @click="increment">增加</button>
<!-- 在事件处理中也可以直接修改 -->
<button @click="count++">直接增加</button>
</template>
ref 接收对象
当 ref() 接收对象类型时,它会自动使用 reactive() 将对象转为响应式:
import { ref } from 'vue'
const user = ref({
name: '张三',
age: 25
})
// 修改值需要 .value
user.value.name = '李四'
user.value.age = 30
// 替换整个对象
user.value = { name: '王五', age: 35 }
深层响应式
ref() 创建的是深层响应式,嵌套对象的修改也会被追踪:
import { ref } from 'vue'
const state = ref({
nested: {
count: 0
},
arr: [1, 2, 3]
})
// 深层修改也是响应式的
state.value.nested.count = 1
state.value.arr.push(4)
reactive()
reactive() 用于创建响应式的对象,它使对象本身变为响应式代理。
基本用法
import { reactive } from 'vue'
const state = reactive({
count: 0,
name: '张三',
isActive: true
})
// 直接访问属性,不需要 .value
console.log(state.count) // 0
console.log(state.name) // '张三'
// 修改属性
state.count++
state.name = '李四'
在模板中使用
<script setup>
import { reactive } from 'vue'
const state = reactive({
count: 0
})
function increment() {
state.count++ // 直接修改
}
</script>
<template>
<p>{{ state.count }}</p>
<button @click="increment">增加</button>
</template>
深层响应式
reactive() 默认创建深层响应式,嵌套对象也会被转为响应式:
import { reactive } from 'vue'
const state = reactive({
user: {
name: '张三',
address: {
city: '北京',
district: '朝阳'
}
},
hobbies: ['读书', '运动']
})
// 所有深层修改都是响应式的
state.user.address.city = '上海'
state.hobbies.push('编程')
// 添加新属性也会是响应式的
state.user.email = '[email protected]'
reactive 的局限性
reactive() 有几个重要的限制需要注意:
1. 只能用于对象类型
// ❌ 错误:不能用于基本类型
const count = reactive(0) // 警告,返回无效值
const name = reactive('张三') // 警告,返回无效值
// ✅ 正确:用于对象类型
const state = reactive({ count: 0 })
const list = reactive([1, 2, 3])
const map = reactive(new Map())
const set = reactive(new Set())
2. 不能替换整个对象
import { reactive } from 'vue'
let state = reactive({ count: 0 })
// ❌ 错误:替换会丢失响应式连接
state = reactive({ count: 1 })
// 原来的响应式对象被丢弃,所有依赖它的地方都不会更新
// ✅ 正确:修改属性
state.count = 1
// ✅ 正确:使用 Object.assign
Object.assign(state, { count: 1, name: '张三' })
3. 解构会丢失响应式
import { reactive } from 'vue'
const state = reactive({
name: '张三',
age: 25
})
// ❌ 错误:解构会丢失响应式
let { name, age } = state
name = '李四' // 只是修改了局部变量,不会触发更新
// ✅ 正确:使用 toRefs
import { toRefs } from 'vue'
const { name, age } = toRefs(state)
name.value = '李四' // 会触发更新
4. 传递属性到函数会丢失响应式
import { reactive } from 'vue'
const state = reactive({ count: 0 })
// ❌ 问题:函数接收的是普通数字,无法追踪变化
function useCount(count) {
// count 是普通数字,不是响应式的
}
useCount(state.count) // 传递的是值,不是引用
// ✅ 正确:传递整个对象
function useCount(state) {
// state 是响应式对象
}
useCount(state)
// ✅ 或者使用 getter 函数
useCount(() => state.count)
Reactive Proxy vs 原始对象
reactive() 返回的是原始对象的 Proxy,两者不相等:
import { reactive } from 'vue'
const raw = { count: 0 }
const proxy = reactive(raw)
console.log(proxy === raw) // false
// 只修改原始对象不会触发更新
raw.count = 1 // ❌ 不会触发更新
// 修改 Proxy 才会触发更新
proxy.count = 1 // ✅ 会触发更新
最佳实践:只使用 reactive() 返回的 Proxy,不要保留对原始对象的引用。
Proxy 的一致性保证
import { reactive } from 'vue'
const raw = { count: 0 }
// 对同一对象多次调用返回同一个 Proxy
const proxy1 = reactive(raw)
const proxy2 = reactive(raw)
console.log(proxy1 === proxy2) // true
// 对 Proxy 调用 reactive 返回自身
console.log(reactive(proxy1) === proxy1) // true
ref vs reactive 对比
| 特性 | ref | reactive |
|---|---|---|
| 支持类型 | 任意类型 | 仅对象类型 |
| 访问方式 | 需要 .value | 直接访问属性 |
| 模板解包 | 自动解包 | 不需要解包 |
| 替换整个值 | 支持 | 不支持 |
| 解构 | 不适用(已是单值) | 需要 toRefs |
| 重新赋值 | 支持 | 不支持 |
| 使用场景 | 单一值、可能被替换的对象 | 一组相关的属性 |
选择建议
// 场景1:基本类型 → 使用 ref
const count = ref(0)
const name = ref('张三')
const isActive = ref(true)
// 场景2:单一对象,可能被整体替换 → 使用 ref
const user = ref(null)
user.value = await fetchUser() // 方便替换
// 场景3:一组相关的属性 → 使用 reactive
const form = reactive({
username: '',
password: '',
remember: false
})
// 场景4:组件内部状态 → 根据需求选择
// 简单状态用 ref
const loading = ref(false)
// 复杂状态用 reactive
const state = reactive({
loading: false,
error: null,
data: []
})
官方推荐
根据 Vue 官方文档的建议:
- 推荐使用
ref()作为声明响应式状态的主要方式 - 对于组件内部的状态,一组相关的属性可以使用
reactive() - 在可复用的组合式函数中,推荐始终返回
ref()以保持一致性和解构友好
toRef() 和 toRefs()
toRef()
toRef() 从响应式对象的单个属性创建 ref,保持响应式连接:
import { reactive, toRef } from 'vue'
const state = reactive({
name: '张三',
age: 25
})
// 为单个属性创建 ref
const name = toRef(state, 'name')
console.log(name.value) // '张三'
// 修改 ref 或原对象,另一个也会同步
state.name = '李四'
console.log(name.value) // '李四'
name.value = '王五'
console.log(state.name) // '王五'
toRefs()
toRefs() 将响应式对象转换为普通对象,每个属性都是 ref:
import { reactive, toRefs } from 'vue'
const state = reactive({
name: '张三',
age: 25,
city: '北京'
})
// 转换为普通对象,属性都是 ref
const { name, age, city } = toRefs(state)
console.log(name.value) // '张三'
console.log(age.value) // 25
// 修改会同步到原对象
name.value = '李四'
console.log(state.name) // '李四'
使用场景:组合式函数返回值
import { reactive, toRefs } from 'vue'
function useUser() {
const state = reactive({
name: '张三',
age: 25,
email: '[email protected]'
})
function updateName(newName) {
state.name = newName
}
// 返回时使用 toRefs,方便调用者解构
return {
...toRefs(state),
updateName
}
}
<script setup>
// 可以直接解构,保持响应式
const { name, age, email, updateName } = useUser()
</script>
<template>
<p>{{ name }} - {{ age }}</p>
<button @click="updateName('李四')">改名</button>
</template>
ref 在 reactive 对象中的解包
当 ref 作为 reactive 对象的属性时,会自动解包:
import { ref, reactive } from 'vue'
const count = ref(0)
const state = reactive({ count })
// 访问时自动解包,不需要 .value
console.log(state.count) // 0
// 修改时也自动解包
state.count = 1
console.log(count.value) // 1
// 注意:赋值新的 ref 会替换连接
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
console.log(count.value) // 1(原来的 ref 不再关联)
数组和集合类型中的 ref 不会解包
import { ref, reactive } from 'vue'
// 数组中的 ref
const books = reactive([ref('Vue 3 Guide')])
// ❌ 需要 .value
console.log(books[0].value) // 'Vue 3 Guide'
// Map 中的 ref
const map = reactive(new Map([['count', ref(0)]]))
// ❌ 需要 .value
console.log(map.get('count').value) // 0
// Set 中的 ref
const set = reactive(new Set([ref('a')]))
set.forEach(item => console.log(item.value)) // 'a'
模板中的 ref 解包规则
在模板中使用 ref 时,只有顶层的 ref 属性才会自动解包:
<script setup>
import { ref } from 'vue'
const count = ref(0)
const object = { id: ref(1) }
</script>
<template>
<!-- ✅ 正确:顶层 ref 自动解包 -->
<p>{{ count }}</p>
<p>{{ count + 1 }}</p>
<!-- ⚠️ 注意:嵌套 ref 在表达式中不解包 -->
<p>{{ object.id + 1 }}</p>
<!-- 结果: "[object Object]1" -->
<!-- ✅ 但在文本插值中会解包 -->
<p>{{ object.id }}</p>
<!-- 结果: 1 -->
</template>
解决方案:解构到顶层
<script setup>
import { ref } from 'vue'
const object = { id: ref(1) }
const { id } = object // 解构到顶层
</script>
<template>
<p>{{ id + 1 }}</p> <!-- 2 -->
</template>
shallowRef() 和 shallowReactive()
shallowRef()
只追踪 .value 的变化,不追踪内部属性:
import { shallowRef, triggerRef } from 'vue'
const state = shallowRef({
nested: { count: 0 }
})
// ✅ 替换整个 value 会触发更新
state.value = { nested: { count: 1 } }
// ❌ 修改深层属性不会触发更新
state.value.nested.count = 2
// 可以手动触发更新
state.value.nested.count = 3
triggerRef(state) // 手动触发
shallowReactive()
只追踪顶层属性的变化:
import { shallowReactive } from 'vue'
const state = shallowReactive({
count: 0,
nested: {
value: 1
}
})
// ✅ 顶层属性变化是响应式的
state.count = 1
// ❌ 嵌套属性变化不是响应式的
state.nested.value = 2 // 不会触发更新
使用场景
import { shallowRef, markRaw } from 'vue'
// 1. 大型数据结构,避免深层响应式开销
const bigList = shallowRef([...])
// 2. 第三方库实例(通常不需要响应式)
import { Chart } from 'chart.js'
const chart = shallowRef(null)
chart.value = new Chart(ctx, config)
// 3. 与外部状态管理集成
const externalState = shallowRef(null)
externalState.value = externalLib.getState()
readonly() 和 shallowReadonly()
readonly()
readonly() 接收一个对象(无论是响应式的还是普通的)或 ref,返回一个只读的代理。这个代理是深层的,任何嵌套属性也都是只读的:
import { reactive, readonly, ref } from 'vue'
// 对 reactive 对象使用
const original = reactive({ count: 0 })
const copy = readonly(original)
// 对 ref 使用
const count = ref(0)
const readonlyCount = readonly(count)
// 对普通对象使用
const plain = { name: '张三' }
const readonlyPlain = readonly(plain)
// ❌ 尝试修改会失败并触发警告
copy.count++ // 警告: Set operation on key "count" failed
readonlyCount.value++ // 警告
响应式连接
readonly() 创建的只读代理与原始对象保持响应式连接。当原始响应式对象变化时,只读代理也会同步更新:
import { reactive, readonly, watchEffect } from 'vue'
const original = reactive({ count: 0 })
const copy = readonly(original)
// 监听只读代理
watchEffect(() => {
console.log('copy.count:', copy.count)
})
// 修改原始对象,只读代理也会更新
original.count++ // 控制台输出: copy.count: 1
实际应用:防止意外修改
import { reactive, readonly } from 'vue'
const state = reactive({
user: { name: '张三' },
settings: { theme: 'dark' }
})
// 暴露给子组件时使用只读版本
export function useUserStore() {
// 内部可以修改
function updateUser(name) {
state.user.name = name
}
// 外部只能读取,不能修改
return {
user: readonly(state.user),
settings: readonly(state.settings),
updateUser
}
}
与 provide/inject 配合
import { provide, reactive, readonly } from 'vue'
const theme = reactive({ mode: 'dark' })
// 提供只读版本,防止子组件意外修改
provide('theme', readonly(theme))
// 同时提供修改方法
provide('setTheme', (newTheme) => {
Object.assign(theme, newTheme)
})
shallowReadonly()
shallowReadonly() 只创建顶层属性的只读代理,嵌套对象仍然可以修改:
import { shallowReadonly } from 'vue'
const state = shallowReadonly({
count: 0,
nested: {
value: 1
}
})
// ❌ 顶层属性不可修改
state.count++ // 警告
// ⚠️ 嵌套属性可以修改(但通常不应该这样做)
state.nested.value = 2 // 允许修改,无警告
使用场景
import { shallowReadonly } from 'vue'
// 当只需要保护顶层配置,但内部数据需要可变时
const config = shallowReadonly({
apiUrl: 'https://api.example.com',
timeout: 5000,
headers: {
// 这个对象仍然可以修改
Authorization: ''
}
})
// 可以更新授权头
config.headers.Authorization = 'Bearer token'
// 但不能修改顶层配置
config.apiUrl = 'https://other.com' // 警告
isReadonly()
检查对象是否是由 readonly() 或 shallowReadonly() 创建的只读代理:
import { reactive, readonly, shallowReadonly, ref, isReadonly } from 'vue'
const state = reactive({ count: 0 })
const readonlyState = readonly(state)
const shallowReadonlyState = shallowReadonly({ nested: {} })
const count = ref(0)
const readonlyCount = readonly(count)
console.log(isReadonly(readonlyState)) // true
console.log(isReadonly(shallowReadonlyState)) // true
console.log(isReadonly(readonlyCount)) // true
console.log(isReadonly(state)) // false
console.log(isReadonly(count)) // false
// 注意:readonly 对象的嵌套属性也是 readonly
console.log(isReadonly(readonlyState.nested)) // true(如果有嵌套对象)
console.log(isReadonly(shallowReadonlyState.nested)) // false
readonly vs shallowReadonly 对比
| 特性 | readonly | shallowReadonly |
|---|---|---|
| 顶层只读 | ✅ | ✅ |
| 嵌套只读 | ✅ | ❌ |
| 响应式连接 | ✅ | ✅ |
| 性能 | 较低(深层转换) | 较高(浅层) |
| 使用场景 | 需要完全保护 | 只需保护顶层 |
工具函数
isRef()
判断一个值是否是 ref:
import { ref, isRef, reactive } from 'vue'
const count = ref(0)
const state = reactive({ count: 0 })
console.log(isRef(count)) // true
console.log(isRef(state)) // false
console.log(isRef(0)) // false
unref()
如果参数是 ref 则返回其值,否则返回参数本身:
import { ref, unref } from 'vue'
const count = ref(0)
console.log(unref(count)) // 0
console.log(unref(1)) // 1
// 等价于
val = isRef(val) ? val.value : val
toValue()
Vue 3.3+ 新增,规范化 ref、getter 或普通值为值:
import { ref, toValue } from 'vue'
const count = ref(0)
toValue(count) // 0
toValue(() => 1) // 1
toValue(2) // 2
在组合式函数中处理参数时很有用,可以让函数接受多种形式的参数:
import { ref, toValue, watchEffect } from 'vue'
// 组合式函数可以接受 ref、getter 或普通值
function useFeature(maybeRefOrGetter) {
// toValue 会自动解包 ref 并调用 getter
watchEffect(() => {
// 在副作用中使用 toValue,确保响应式追踪
const value = toValue(maybeRefOrGetter)
console.log('值变化:', value)
})
}
// 使用示例
const count = ref(0)
// 可以接受 ref
useFeature(count)
// 可以接受 getter 函数
useFeature(() => count.value + 1)
// 可以接受普通值
useFeature('hello')
// 这使得组合式函数非常灵活
function useFetch(url) {
const data = ref(null)
const error = ref(null)
const loading = ref(false)
watchEffect(async () => {
// toValue 让我们可以接受 ref 或 getter
const urlValue = toValue(url)
if (!urlValue) return
loading.value = true
try {
const response = await fetch(urlValue)
data.value = await response.json()
} catch (e) {
error.value = e
} finally {
loading.value = false
}
})
return { data, error, loading }
}
// 使用
const endpoint = ref('/api/users')
const { data } = useFetch(endpoint) // 接受 ref
const { data: data2 } = useFetch(() => `/api/users/${userId.value}`) // 接受 getter
isReactive()
判断对象是否是 reactive() 创建的响应式代理:
import { reactive, isReactive, ref } from 'vue'
const state = reactive({ count: 0 })
const count = ref(0)
console.log(isReactive(state)) // true
console.log(isReactive(count)) // false
console.log(isReactive({})) // false
isProxy()
判断对象是否是 Vue 创建的响应式代理(reactive 或 readonly):
import { reactive, readonly, ref, isProxy } from 'vue'
const state = reactive({ count: 0 })
const readonlyState = readonly({ count: 0 })
const count = ref(0)
console.log(isProxy(state)) // true
console.log(isProxy(readonlyState)) // true
console.log(isProxy(count)) // false
完整示例
计数器示例
<script setup>
import { ref, reactive } from 'vue'
// 使用 ref
const count = ref(0)
function increment() {
count.value++
}
function reset() {
count.value = 0
}
// 使用 reactive
const counter = reactive({
count: 0,
lastUpdate: null,
history: []
})
function incrementReactive() {
counter.history.push(counter.count)
counter.count++
counter.lastUpdate = new Date().toLocaleTimeString()
}
</script>
<template>
<div>
<h3>使用 ref</h3>
<p>计数: {{ count }}</p>
<button @click="increment">增加</button>
<button @click="reset">重置</button>
<h3>使用 reactive</h3>
<p>计数: {{ counter.count }}</p>
<p>最后更新: {{ counter.lastUpdate || '未更新' }}</p>
<p>历史: {{ counter.history.join(', ') || '无' }}</p>
<button @click="incrementReactive">增加</button>
</div>
</template>
表单状态管理
<script setup>
import { reactive, toRefs } from 'vue'
const form = reactive({
username: '',
email: '',
password: '',
remember: false
})
function submit() {
console.log('提交表单:', { ...form })
}
function reset() {
Object.assign(form, {
username: '',
email: '',
password: '',
remember: false
})
}
// 可以解构使用
const { username, email, password, remember } = toRefs(form)
</script>
<template>
<form @submit.prevent="submit">
<div>
<label>用户名:</label>
<input v-model="form.username" />
</div>
<div>
<label>邮箱:</label>
<input v-model="form.email" type="email" />
</div>
<div>
<label>密码:</label>
<input v-model="form.password" type="password" />
</div>
<div>
<label>
<input v-model="form.remember" type="checkbox" />
记住我
</label>
</div>
<button type="submit">提交</button>
<button type="button" @click="reset">重置</button>
</form>
</template>
响应式 Props 解构(Vue 3.5+)
Vue 3.5 引入了响应式 Props 解构功能,这是对 defineProps() 返回值解构的重大改进。在此之前,解构 props 会丢失响应式连接。
传统问题
在 Vue 3.4 及更早版本中,解构 props 会丢失响应式:
// Vue 3.4 及更早版本
const { foo } = defineProps(['foo'])
watchEffect(() => {
console.log(foo) // 只执行一次,后续 props.foo 变化不会触发
})
这是因为 foo 只是一个普通常量,保存的是解构时的值快照,与 props.foo 的响应式连接已断开。
Vue 3.5 的解决方案
Vue 3.5 的编译器会自动处理解构的 props 变量,在访问时自动添加 props. 前缀:
<script setup>
// 编译器会自动处理
const { foo } = defineProps(['foo'])
// 等价于:console.log(props.foo)
console.log(foo)
watchEffect(() => {
// 等价于:console.log(props.foo)
// 现在 foo 变化时会重新执行!
console.log(foo)
})
</script>
声明默认值
响应式 Props 解构让默认值的声明更加简洁,直接使用 JavaScript 原生语法:
<script setup lang="ts">
// 方式一:使用类型声明 + 解构默认值
const {
count = 0, // 默认值为 0
message = 'hello', // 默认值为 'hello'
enabled = true // 默认值为 true
} = defineProps<{
count?: number
message?: string
enabled?: boolean
}>()
</script>
对比传统的 withDefaults 语法:
<script setup lang="ts">
// 传统方式:更冗长
const props = withDefaults(
defineProps<{
count?: number
message?: string
enabled?: boolean
}>(),
{
count: 0,
message: 'hello',
enabled: true
}
)
</script>
使用限制
虽然解构后的变量保持响应式,但在某些场景下需要特殊处理:
传递给 watch
const { foo } = defineProps(['foo'])
// ❌ 错误:watch 接收的是值而非响应式源
watch(foo, (newVal) => { /* ... */ })
// 等价于 watch(props.foo, ...),props.foo 是一个值
// ✅ 正确:使用 getter 函数
watch(() => foo, (newVal) => { /* ... */ })
传递给 Composable
const { userId } = defineProps(['userId'])
// ❌ 错误:传递的是值,失去响应式
useUser(userId)
// ✅ 正确:使用 getter 函数保持响应式
useUser(() => userId)
// composable 内部使用 toValue() 处理
function useUser(getter) {
watchEffect(() => {
fetch(`/api/user/${toValue(getter)}`)
})
}
实际应用示例
<script setup lang="ts">
import { watchEffect, computed } from 'vue'
// 响应式解构 props,带默认值
const {
title = '默认标题',
pageSize = 10,
currentPage = 1
} = defineProps<{
title?: string
pageSize?: number
currentPage?: number
}>()
// 计算属性可以直接使用解构的变量
const displayTitle = computed(() => `[${title}]`)
// watchEffect 可以正常追踪变化
watchEffect(() => {
console.log('当前页:', currentPage)
})
// 传递给其他函数时使用 getter
const { data } = useFetch(() => `/api/posts?page=${currentPage}&size=${pageSize}`)
</script>
<template>
<h1>{{ displayTitle }}</h1>
<p>第 {{ currentPage }} 页,每页 {{ pageSize }} 条</p>
</template>
与 toRefs 的对比
响应式 Props 解构与 toRefs 作用于 reactive 对象的行为相似,但有本质区别:
// reactive 对象:解构后需要 toRefs 保持响应式
const state = reactive({ count: 0 })
const { count } = toRefs(state) // count 是 ref,需要 .value
// props:Vue 3.5 解构后直接保持响应式
const { count } = defineProps(['count']) // count 是普通值访问方式
关键区别:
| 特性 | toRefs(reactive) | 响应式 Props 解构 |
|---|---|---|
| 返回值类型 | ref(需要 .value) | 编译器自动处理(不需要 .value) |
| 响应式保持 | 通过 ref 的 getter/setter | 编译时自动添加 props. 前缀 |
| 传递给函数 | 需要传递 ref 本身 | 使用 getter 函数 |
小结
- reactive():创建响应式对象,深层响应式
- ref vs reactive:了解两者的区别、局限性和使用场景
- toRef/toRefs:在解构时保持响应式连接
- ref 解包规则:reactive 对象中、数组/集合中、模板中的不同行为
- shallowRef/shallowReactive:浅层响应式,优化性能
- 工具函数:isRef、unref、toValue、isReactive、isProxy
最佳实践总结:
- 推荐使用
ref()作为主要的响应式声明方式 - 基本类型必须使用
ref() - 一组相关属性可使用
reactive() - 组合式函数返回值推荐使用
ref() - 解构 reactive 对象时使用
toRefs() - 大型数据结构考虑使用 shallow 版本优化性能
练习
- 分别用
ref和reactive创建一个待办事项列表 - 实现一个使用
toRefs的表单状态管理 - 创建一个组合式函数,返回 ref 类型的状态