跳到主要内容

Vue ref 和 reactive

在 Vue 3 的组合式 API 中,refreactive 是两个最常用的响应式 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() 有一些限制:

  1. 只能接收对象:不能直接接收基本类型
  2. 替换整个对象会丢失响应式
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 对比

特性refreactive
支持类型基本类型 + 对象仅对象
访问方式需要 .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 的内容:

  1. ref():创建响应式数据,支持基本类型和对象
  2. reactive():创建响应式对象,深度响应式
  3. ref vs reactive:了解两者的区别和使用场景
  4. toRef/toRefs:在解构时保持响应式
  5. shallowRef/shallowReactive:浅层响应式
  6. isRef():判断是否为 ref

练习

  1. 分别用 ref 和 reactive 创建待办事项列表
  2. 实现一个表单状态管理
  3. 创建一个自定义 Hook,复用响应式逻辑

准备好进入下一章,学习 computed 和 watch 的内容了吗?