响应式处理函数
Vue 3 引入了全新的响应式系统,提供了 ref()
、reactive()
、computed()
、watch()
和 watchEffect()
等 API。这些 API 在开发中各有其适用场景,但很多开发者对它们之间的区别和使用时机存在困惑。本文将从原理、用法和最佳实践角度深入分析这些响应式 API。
1. ref() - 基础响应式容器
核心特性
ref()
是 Vue 3 中最基础的响应式 API,它的核心作用是创建一个包含响应式值的引用对象。
import { ref } from 'vue'
// 创建 ref
const count = ref(0)
const message = ref('Hello')
const isActive = ref(false)
// 访问和修改值
console.log(count.value) // 0
count.value++ // 修改值
实现原理
ref()
内部使用 JavaScript 的访问器属性(getter/setter)实现响应式:
// 简化的 ref 实现原理
function myRef(value) {
let _value = value
return {
get value() {
// 依赖收集
track(this, 'value')
return _value
},
set value(newValue) {
if (newValue !== _value) {
_value = newValue
// 触发更新
trigger(this, 'value')
}
}
}
}
模板中的自动解包
在模板中使用时,Vue 会自动解包 ref,无需使用 .value
:
<template>
<div>
<p>{{ count }}</p> <!-- 自动解包,无需 count.value -->
<button @click="count++">Increment</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
引用类型值的处理
当 ref()
接收对象或数组时,内部会使用 reactive()
进行转换:
const user = ref({
name: 'John',
age: 30
})
// 实际上相当于
const user = {
value: reactive({
name: 'John',
age: 30
})
}
2. reactive() - 对象响应式代理
核心特性
reactive()
专门用于创建响应式的对象或数组,它返回原始对象的 Proxy 代理。
import { reactive } from 'vue'
// 创建响应式对象
const state = reactive({
count: 0,
user: {
name: 'John',
address: {
city: 'New York'
}
},
items: ['apple', 'banana']
})
// 直接访问属性,无需 .value
console.log(state.count) // 0
state.count++ // 修改属性
state.user.name = 'Jane' // 深层属性也是响应式的
state.items.push('orange') // 数组操作也是响应式的
实现原理
reactive()
使用 ES6 Proxy 实现深度响应式:
// 简化的 reactive 实现原理
function myReactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
// 依赖收集
track(target, key)
const result = Reflect.get(target, key, receiver)
// 如果结果是对象,递归代理
if (result && typeof result === 'object') {
return myReactive(result)
}
return result
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (result && oldValue !== value) {
// 触发更新
trigger(target, key)
}
return result
}
})
}
局限性
- 仅限于对象类型:不能用于基本类型
- 解构丢失响应性:直接解构会失去响应式连接
- 整体替换丢失响应性:直接替换整个对象会破坏响应式
// 错误示例:解构会失去响应性
const state = reactive({ count: 0, name: 'John' })
const { count, name } = state // count 和 name 不再是响应式的
// 正确做法:使用 toRefs 保持响应性
import { toRefs } from 'vue'
const { count, name } = toRefs(state) // count 和 name 现在是 ref
// 错误示例:整体替换会破坏响应式
state = { count: 1, name: 'Jane' } // 错误!
// 正确做法:逐个修改属性
state.count = 1
state.name = 'Jane'
3. ref() 与 reactive() 的深度对比
相同点
- 都是响应式的:都能触发视图更新
- 都支持深度响应:嵌套对象都是响应式的
- 都基于 Vue 响应式系统:使用相同的依赖收集和触发机制
不同点
特性 | ref() | reactive() |
---|---|---|
适用类型 | 任何类型 | 仅对象和数组 |
访问方式 | 需要 .value | 直接访问属性 |
模板使用 | 自动解包 | 直接使用 |
解构行为 | 保持响应式 | 失去响应式 |
类型推导 | 需要泛型参数 | 自动推断 |
整体替换 | 支持(修改 .value ) | 不支持 |
使用场景建议
使用 ref() 当:
- 处理基本类型值(string、number、boolean)
- 需要明确的
.value
访问语义 - 可能需要在未来重新赋值整个对象
- 在组合式函数中返回值
使用 reactive() 当:
- 处理一组逻辑相关的状态
- 需要管理复杂对象或嵌套数据
- 希望直接访问属性而不需要
.value
- 状态结构相对稳定,不需要整体替换
4. computed() - 计算属性
核心特性
computed()
创建基于依赖缓存的响应式计算值。
import { ref, computed } from 'vue'
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
console.log(doubleCount.value) // 0
count.value = 5
console.log(doubleCount.value) // 10
可写计算属性
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(newValue) {
const [first, last] = newValue.split(' ')
firstName.value = first
lastName.value = last || ''
}
})
fullName.value = 'Jane Smith' // 会自动更新 firstName 和 lastName
5. watch() 与 watchEffect() - 副作用监听
watch() - 精确监听
import { ref, watch } from 'vue'
const count = ref(0)
const user = reactive({ name: 'John', age: 30 })
// 监听单个 ref
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`)
})
// 监听 reactive 的属性
watch(() => user.name, (newName, oldName) => {
console.log(`Name changed from ${oldName} to ${newName}`)
})
// 监听多个源
watch([count, () => user.age], ([newCount, newAge], [oldCount, oldAge]) => {
// 处理变化
})
watchEffect() - 自动依赖收集
import { ref, watchEffect } from 'vue'
const count = ref(0)
const message = ref('')
// 自动收集依赖
watchEffect(() => {
console.log(`Count is ${count.value}, message is ${message.value}`)
// 当 count 或 message 变化时都会执行
})
对比总结
特性 | watch() | watchEffect() |
---|---|---|
依赖收集 | 显式指定 | 自动收集 |
立即执行 | 可配置 | 总是立即执行 |
旧值获取 | 支持 | 不支持 |
使用场景 | 需要精确控制 | 自动响应依赖变化 |
总结
Vue 3 的响应式 API 提供了强大而灵活的状态管理能力:
ref()
:适用于基本类型和需要明确访问语义的场景,总是使用.value
访问reactive()
:适用于逻辑相关的对象状态,直接访问属性但注意解构问题computed()
:用于派生状态,自动缓存计算结果watch()
:精确监听特定数据源的变化,可获取旧值watchEffect()
:自动追踪依赖,立即执行副作用