Vue ref 和 reactive
在 Vue 3 的组合式 API 中,ref 和 reactive 是两个最常用的响应式 API。本章将详细介绍它们的用法和区别。
ref()
ref() 用于创建响应式的数据,可以是基本类型或对象类型。
基本用法
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) // '张三'
// 修改值
count.value++
name.value = '李四'
在模板中使用
在模板中使用时,Vue 会自动解包,不需要 .value:
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<p>{{ count }}</p> <!-- 自动解包,显示 0 -->
<button @click="increment">增加</button>
</template>
ref() 的响应式原理
ref() 内部使用了 JavaScript 的 Proxy 来实现响应式:
┌─────────────────────────────────────────────────────────┐
│ ref 对象结构 │
├─────────────────────────────────────────────────────────┤
│ │
│ ref(0) │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ _rawValue │ 原始值: 0 │
│ │ _value │ 响应式包装后的值 │
│ │ __v_ref │ 标记为 ref │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
ref 接收对象
ref 也能接收对象,内部会自动转换为 reactive:
import { ref } from 'vue'
const user = ref({
name: '张三',
age: 25
})
// 修改深层属性也需要 .value
user.value.name = '李四'
user.value.age = 30
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 = '李四'
reactive 的限制
reactive() 有一些限制:
- 只能接收对象:不能直接接收基本类型
- 替换整个对象会丢失响应式:
import { reactive } from 'vue'
const state = reactive({ count: 0 })
// 替换整个对象 - 会丢失响应式
state = reactive({ count: 1 }) // ❌ 错误
// 正确做法 - 替换属性
state.count = 1 // ✅
深层响应式
reactive() 默认创建深层响应式:
import { reactive } from 'vue'
const state = reactive({
user: {
name: '张三',
address: {
city: '北京'
}
},
hobbies: ['读书', '运动']
})
// 深层修改也是响应式的
state.user.address.city = '上海' // ✅ 响应式
state.hobbies.push('编程') // ✅ 响应式
ref vs reactive 对比
| 特性 | ref | reactive |
|---|---|---|
| 支持类型 | 基本类型 + 对象 | 仅对象 |
| 访问方式 | 需要 .value | 直接访问属性 |
| 模板解包 | 自动解包 | 不自动解包(需用 .toRef) |
| 重新赋值 | 支持 | 不支持直接赋值 |
| 解构 | 支持(需使用 toRef) | 支持(需使用 toRefs) |
使用建议
// 基本类型 → 使用 ref
const count = ref(0)
const name = ref('张三')
const isActive = ref(true)
// 对象类型 → 两者皆可,但推荐 reactive
const user = ref({ name: '张三', age: 25 })
const state = reactive({ user: { name: '张三', age: 25 } })
// 推荐:根据数据性质选择
// 单一值 → ref
// 相关联的多个值 → reactive
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
})
// 转换为普通对象,属性都是 ref
const { name, age } = toRefs(state)
console.log(name.value) // '张三'
console.log(age.value) // 25
使用场景
解构响应式对象时使用 toRefs:
import { reactive, toRefs } from 'vue'
function useUser() {
const state = reactive({
name: '张三',
age: 25,
email: '[email protected]'
})
// 返回时解构,方便组件使用
return toRefs(state)
}
<script setup>
const { name, age, email } = useUser()
</script>
<template>
<p>{{ name }} - {{ age }}</p>
</template>
isRef()
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
shallowRef() 和 shallowReactive()
shallowRef()
只追踪顶层变化的 ref:
import { shallowRef } from 'vue'
const state = shallowRef({
count: 0,
deep: {
value: 1
}
})
state.value.count = 1 // ✅ 触发更新
state.value.deep.value = 2 // ❌ 不会触发更新(除非 triggerRef)
// 需要手动触发
import { triggerRef } from 'vue'
state.value.deep.value = 2
triggerRef(state)
shallowReactive()
只追踪顶层属性变化的响应式对象:
import { shallowReactive } from 'vue'
const state = shallowReactive({
count: 0,
deep: {
value: 1
}
})
state.count = 1 // ✅ 响应式
state.deep.value = 2 // ❌ 不响应式
示例:计数器
<script setup>
import { ref, reactive } from 'vue'
// 使用 ref
const count = ref(0)
function increment() {
count.value++
}
// 使用 reactive
const counter = reactive({
count: 0,
lastUpdate: null
})
function incrementReactive() {
counter.count++
counter.lastUpdate = new Date().toLocaleTimeString()
}
</script>
<template>
<div>
<h3>使用 ref</h3>
<p>计数: {{ count }}</p>
<button @click="increment">增加</button>
<h3>使用 reactive</h3>
<p>计数: {{ counter.count }}</p>
<p>最后更新: {{ counter.lastUpdate }}</p>
<button @click="incrementReactive">增加</button>
</div>
</template>
小结
本章我们详细学习了 Vue 响应式 API 的内容:
- ref():创建响应式数据,支持基本类型和对象
- reactive():创建响应式对象,深度响应式
- ref vs reactive:了解两者的区别和使用场景
- toRef/toRefs:在解构时保持响应式
- shallowRef/shallowReactive:浅层响应式
- isRef():判断是否为 ref
练习
- 分别用 ref 和 reactive 创建待办事项列表
- 实现一个表单状态管理
- 创建一个自定义 Hook,复用响应式逻辑
准备好进入下一章,学习 computed 和 watch 的内容了吗?