在Vue 3的响应式系统中,ref
和reactive
是最核心的API。许多开发者在使用时容易混淆两者的区别,本文将通过用法对比、原理分析和源码解读,帮助开发者深入理解这两个API的设计思想。
import { ref } from 'vue'
// 基本类型
const count = ref(0)
console.log(count.value) // 0
// 引用类型
const objRef = ref({ name: 'Vue' })
console.log(objRef.value.name) // Vue
import { reactive } from 'vue'
const state = reactive({
count: 0,
user: { name: 'John' }
})
console.log(state.count) // 0
特性 | ref | reactive |
---|---|---|
数据类型支持 | 所有类型 | 对象/数组 |
访问方式 | 需要.value |
直接访问属性 |
响应式保持 | 替换整个对象仍保持响应 | 结构破坏会丢失响应 |
模板自动解包 | 支持 | 无需解包 |
TypeScript支持 | 明确类型推断 | 嵌套属性类型保留 |
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep = new Dep()
constructor(value: T) {
this._rawValue = value
this._value = convertToReactive(value)
}
get value() {
trackRefValue(this) // 依赖收集
return this._value
}
set value(newVal) {
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = convertToReactive(newVal)
triggerRefValue(this) // 触发更新
}
}
}
核心机制:通过对象包装 + 属性劫持实现响应式
function reactive(target) {
const proxy = new Proxy(target, {
get(target, key, receiver) {
track(target, key) // 依赖收集
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (hasChanged(value, oldValue)) {
trigger(target, key) // 触发更新
}
return result
}
})
return proxy
}
核心机制:基于ES6 Proxy实现深层次响应式代理
推荐使用ref:
推荐使用reactive:
// 错误示例:解构响应式对象
const { count } = reactive({ count: 0 }) // 失去响应性
// 正确做法
const state = reactive({ count: 0 })
const count = toRef(state, 'count')
在模板编译阶段,编译器会自动处理.value
:
// 模板代码
<template>{{ count }}</template>
// 编译后
_createVNode(..., unref(count))
通过WeakMap
缓存代理对象:
const proxyMap = new WeakMap()
function reactive(target) {
if (proxyMap.has(target)) {
return proxyMap.get(target)
}
// 创建新代理...
}
理解ref
和reactive
的区别需要从设计目的出发:
ref
是"包装器",解决值类型响应问题reactive
是"代理器",处理对象深度响应通过组合使用这两个API,配合toRefs
等工具函数,可以构建出既高效又易维护的响应式系统。
扩展阅读:
希望这篇文章能帮助您更好地驾驭Vue 3的响应式系统!欢迎在评论区交流讨论。