在 Vue 3 的响应式系统中,ref 是最基础也是最核心的响应式单元。
几乎所有的 reactive、computed、watchEffect 等能力都与 ref 紧密相关。
本文我们从源码入手,逐行讲解 Vue3 的 ref 实现原理。
一、ref 是什么
ref 的本质是一个带有 .value 属性的响应式对象。
当 .value 改变时,依赖它的副作用(effect)就会重新执行。
简单示例:
javascript
import { ref, effect } from 'vue'
const count = ref(0)
effect(() => {
console.log(count.value)
})
count.value++ // 触发 effect,打印新值
我们来看看 Vue3 中它是怎么实现的。
二、源码结构总览
文件路径:
bash
packages/reactivity/src/ref.ts
主要包含以下部分:
Ref接口定义isRef/ref/shallowRef方法RefImpl类(核心实现)triggerRef、unref、toValue等辅助函数toRef、toRefs、proxyRefs等工具- 一系列类型定义(UnwrapRef、ShallowUnwrapRef 等)
三、Ref 接口定义
typescript
export interface Ref<T = any, S = T> {
get value(): T
set value(_: S)
[RefSymbol]: true // 用私有 Symbol 区分类型(避免出现在自动提示中)
}
👉 这里定义了一个具有 value 访问器的响应式引用类型。
所有的 ref() 调用最终都会生成一个实现了该接口的对象。
四、isRef 判断函数
arduino
export function isRef(r: any): r is Ref {
return r ? r[ReactiveFlags.IS_REF] === true : false
}
逻辑非常简单:
- 判断对象上是否存在
ReactiveFlags.IS_REF(即__v_isRef)标志位。 - 这是 Vue 内部区分"是否为 ref 实例"的方式。
五、ref() 函数入口
javascript
export function ref(value?: unknown) {
return createRef(value, false)
}
ref() 就是对 createRef 的封装。
第二个参数 false 表示不是浅层 ref(即内部值也会被响应化)。
六、shallowRef()
javascript
export function shallowRef(value?: unknown) {
return createRef(value, true)
}
与普通 ref 的唯一区别是:
- 它不会递归地把对象内部变为响应式。
- 只有
.value层级被追踪。
示例:
ini
const state = shallowRef({ count: 1 })
state.value.count++ // 不触发更新
state.value = { count: 2 } // 触发更新
七、createRef 函数
php
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
功能:
- 若传入本身就是 ref,则直接返回;
- 否则创建新的
RefImpl实例。
八、RefImpl 核心类
这是整个 ref 响应式系统的核心实现。
typescript
class RefImpl<T = any> {
_value: T
private _rawValue: T
dep: Dep = new Dep()
public readonly [ReactiveFlags.IS_REF] = true
public readonly [ReactiveFlags.IS_SHALLOW]: boolean = false
constructor(value: T, isShallow: boolean) {
this._rawValue = isShallow ? value : toRaw(value)
this._value = isShallow ? value : toReactive(value)
this[ReactiveFlags.IS_SHALLOW] = isShallow
}
这里:
_rawValue存原始值;_value存转换后的响应式值;dep是依赖收集容器;ReactiveFlags.IS_REF标志这是一个 ref 对象。
getter 部分
kotlin
get value() {
if (__DEV__) {
this.dep.track({
target: this,
type: TrackOpTypes.GET,
key: 'value',
})
} else {
this.dep.track()
}
return this._value
}
当访问 .value 时:
- 触发依赖收集(
track) - 将当前 effect 注册到
dep中 - 返回内部值
setter 部分
kotlin
set value(newValue) {
const oldValue = this._rawValue
const useDirectValue =
this[ReactiveFlags.IS_SHALLOW] ||
isShallow(newValue) ||
isReadonly(newValue)
newValue = useDirectValue ? newValue : toRaw(newValue)
if (hasChanged(newValue, oldValue)) {
this._rawValue = newValue
this._value = useDirectValue ? newValue : toReactive(newValue)
if (__DEV__) {
this.dep.trigger({
target: this,
type: TriggerOpTypes.SET,
key: 'value',
newValue,
oldValue,
})
} else {
this.dep.trigger()
}
}
}
主要逻辑:
- 判断是否需要递归响应化;
- 若值发生变化(
hasChanged); - 更新
_value和_rawValue; - 通知所有依赖(
trigger)。
九、triggerRef
csharp
export function triggerRef(ref: Ref): void {
if ((ref as any).dep) {
(ref as any).dep.trigger()
}
}
这个函数用于手动触发 shallowRef 的依赖更新。
例如:
ini
const shallow = shallowRef({ greet: 'hi' })
shallow.value.greet = 'hello'
triggerRef(shallow) // 手动触发更新
十、unref 与 toValue
csharp
export function unref<T>(ref: MaybeRef<T>): T {
return isRef(ref) ? ref.value : ref
}
unref 是个语法糖:
- 如果参数是 ref,则返回
.value - 否则直接返回原值
而 toValue 是 unref 的扩展:
- 如果传入函数,会自动调用。
bash
export function toValue<T>(source: MaybeRefOrGetter<T>): T {
return isFunction(source) ? source() : unref(source)
}
十一、proxyRefs
typescript
export function proxyRefs<T extends object>(objectWithRefs: T): ShallowUnwrapRef<T> {
return isReactive(objectWithRefs)
? objectWithRefs
: new Proxy(objectWithRefs, shallowUnwrapHandlers)
}
作用:创建一个代理对象,访问时自动"解包 ref"。
例如:
sql
const user = proxyRefs({ name: ref('Tom') })
console.log(user.name) // 直接得到 'Tom',不用 .value
十二、toRefs 与 toRef
toRefs
把一个响应式对象的每个属性都转为 ref:
typescript
export function toRefs<T extends object>(object: T): ToRefs<T> {
const ret: any = isArray(object) ? new Array(object.length) : {}
for (const key in object) {
ret[key] = propertyToRef(object, key)
}
return ret
}
toRef
针对单个属性创建同步 ref:
vbnet
export function toRef(object, key, defaultValue?) {
return propertyToRef(object, key, defaultValue)
}
示例:
ini
const state = reactive({ foo: 1 })
const fooRef = toRef(state, 'foo')
fooRef.value++ // state.foo 也随之变化
十三、Ref 类型展开(UnwrapRef)
Vue 中大量使用 UnwrapRef<T> 来推导类型。
swift
export type UnwrapRef<T> =
T extends ShallowRef<infer V> ? V
: T extends Ref<infer V> ? UnwrapRefSimple<V>
: UnwrapRefSimple<T>
作用是把 Ref<number> 展开为 number,从而在模板或逻辑中直接使用。
十四、总结
ref 是 Vue3 响应式系统的最小原子单元。
它的实现基于以下机制:
- 每个 ref 对象内部维护一个依赖集合(Dep)
- 访问
.value时进行依赖收集(track) - 修改
.value时触发依赖更新(trigger) - 支持 shallow、custom、toRef、toRefs 等扩展用法
- 与 reactive 一起构成完整的响应式系统
一句话总结:
ref 是响应式系统中的"最小反应单元",为单值状态提供自动追踪能力。
本文内容由人工智能生成,仅供学习与参考使用,请在实际应用中结合自身情况进行判断。