一、引言
在Vue3里,ref
是构建响应式系统的关键部分。它们为开发者提供了便捷的途径来创建响应式数据,使得数据变化时能自动更新DOM。本文会深入探究 ref
的源码,以揭示其实现原理。
二、ref 的源码解析
2.1 基本使用回顾
在正式分析源码前,先回顾下 ref
的基本使用方式:
vue
<template>
<div>
<p>{{ count }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
const increment = () => {
count.value++;
};
</script>
2.2 源码实现
在Vue3的源码中,ref
函数的实现如下:
ts
// packages\reactivity\src\ref.ts
import {
type IfAny, // 导入 IfAny 类型,用于条件类型判断
hasChanged, // 导入 hasChanged 函数,用于比较值是否发生变化
isArray, // 导入 isArray 函数,用于判断是否为数组
isFunction, // 导入 isFunction 函数,用于判断是否为函数
isObject, // 导入 isObject 函数,用于判断是否为对象
} from '@vue/shared' // 从 @vue/shared 模块中导入需使用的工具函数
import { Dep, getDepFromReactive } from './dep' // 导入依赖管理类和获取依赖的函数
import {
type Builtin, // 导入内置类型
type ShallowReactiveMarker, // 导入浅反应式标记
isProxy, // 导入 isProxy 函数,用于判断是否为代理对象
isReactive, // 导入 isReactive 函数,用于判断是否为响应式对象
isReadonly, // 导入 isReadonly 函数,用于判断是否为只读对象
isShallow, // 导入 isShallow 函数,用于判断是否为浅层对象
toRaw, // 导入 toRaw 函数,用于获取原始值
toReactive, // 导入 toReactive 函数,用于将普通对象转换为响应式对象
} from './reactive' // 从 './reactive' 模块中导入需要的函数和类型
import type { ComputedRef, WritableComputedRef } from './computed' // 导入计算属性相关类型
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants' // 导入一些常量, 用于标识和追踪操作类型
import { warn } from './warning' // 导入警告函数,用于输出警告信息
declare const RefSymbol: unique symbol // 声明一个唯一的符号,用于表示 Ref 类型
export declare const RawSymbol: unique symbol // 声明一个唯一的符号,用于表示 raw 类型
// 定义 Ref 接口,表示响应式引用类型
export interface Ref<T = any, S = T> {
get value(): T // 获取 .value 的值
set value(_: S) // 设置 .value 的值
[RefSymbol]: true // 使用唯一符号作为类型区分
}
/**
* 检查一个值是否为 ref 对象。
*
* @param r - 要检查的值。
* @see {@link https://vuejs.org/api/reactivity-utilities.html#isref}
*/
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T> // 声明一个泛型函数,检查是否为 Ref 类型
export function isRef(r: any): r is Ref { // 函数重载实现
return r ? r[ReactiveFlags.IS_REF] === true : false // 检查传入的对象是否含有 IS_REF 标志
}
/**
* 接受一个内部值并返回一个可反应和可变的 ref 对象,该对象具有一个指向内部值的 .value 属性。
*
* @param value - 要包装的对象。
* @see {@link https://vuejs.org/api/reactivity-core.html#ref}
*/
export function ref<T>( // 定义 ref 函数,返回 Ref 类型
value: T,
): [T] extends [Ref] ? IfAny<T, Ref<T>, T> : Ref<UnwrapRef<T>, UnwrapRef<T> | T> // 依据传入值的类型返回相应的 Ref 类型
export function ref<T = any>(): Ref<T | undefined> // 函数重载声明
export function ref(value?: unknown) { // 函数实现
return createRef(value, false) // 创建一个响应式引用,非浅层
}
// 声明一个唯一的符号,用于标记浅层引用
declare const ShallowRefMarker: unique symbol
export type ShallowRef<T = any, S = T> = Ref<T, S> & { // 声明 ShallowRef 类型,扩展 Ref
[ShallowRefMarker]?: true // 添加浅层引用的标记
}
/**
* ref() 的浅层版本。
*
* @example
* ```js
* const state = shallowRef({ count: 1 }) // 创建一个浅层引用对象
* state.value.count = 2 // 不会触发变化
* state.value = { count: 2 } // 会触发变化
* ```
*
* @param value - 浅层 ref 的"内部值"。
* @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowref}
*/
export function shallowRef<T>( // 定义 shallowRef 函数,返回 ShallowRef 类型
value: T,
): Ref extends T
? T extends Ref
? IfAny<T, ShallowRef<T>, T> // 根据条件返回相应类型
: ShallowRef<T>
: ShallowRef<T>
export function shallowRef<T = any>(): ShallowRef<T | undefined> // 函数重载声明
export function shallowRef(value?: unknown) { // 函数实现
return createRef(value, true) // 创建一个浅层引用
}
function createRef(rawValue: unknown, shallow: boolean) { // 创建响应式引用的函数
if (isRef(rawValue)) { // 如果原值已是 ref,直接返回
return rawValue
}
return new RefImpl(rawValue, shallow) // 否则返回新创建的 RefImpl 实例
}
/**
* @internal
*/
class RefImpl<T = any> { // 实现 Ref 接口的类
_value: T // 实际的值
private _rawValue: T // 原始值
dep: Dep = new Dep() // 创建一个新的依赖实例
public readonly [ReactiveFlags.IS_REF] = true // 标记该对象是一个 Ref
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 // 设置是否为浅层标记
}
get value() { // 获取值的 getter
if (__DEV__) { // 开发环境判断
this.dep.track({ // 追踪依赖
target: this, // 当前对象
type: TrackOpTypes.GET, // 操作类型
key: 'value', // 键为 'value'
})
} else {
this.dep.track() // 非开发环境直接追踪
}
return this._value // 返回实际值
}
set value(newValue) { // 设置值的 setter
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() // 非开发环境直接触发
}
}
}
}
/**
* 强制触发依赖于浅层引用的效果。通常在对浅层引用的内部值进行深度修改后使用。
*
* @example
* ```js
* const shallow = shallowRef({
* greet: 'Hello, world'
* })
*
* watchEffect(() => {
* console.log(shallow.value.greet) // 第一次运行时输出
* })
*
* shallow.value.greet = 'Hello, universe' // 这不会触发效果
*
* triggerRef(shallow) // 触发效果
* ```
*
* @param ref - 要执行其关联效果的 ref。
* @see {@link https://vuejs.org/api/reactivity-advanced.html#triggerref}
*/
export function triggerRef(ref: Ref): void { // 触发浅层引用的函数
if ((ref as unknown as RefImpl).dep) { // 检查 ref 是否有依赖
if (__DEV__) { // 开发环境判断
;(ref as unknown as RefImpl).dep.trigger({ // 触发依赖更新
target: ref,
type: TriggerOpTypes.SET,
key: 'value',
newValue: (ref as unknown as RefImpl)._value, // 获取当前值
})
} else {
;(ref as unknown as RefImpl).dep.trigger() // 非开发环境直接触发
}
}
}
export type MaybeRef<T = any> = // 定义 MaybeRef 类型,可能是值或 Ref
| T
| Ref<T>
| ShallowRef<T>
| WritableComputedRef<T>
export type MaybeRefOrGetter<T = any> = MaybeRef<T> | ComputedRef<T> | (() => T) // 定义可能的引用或函数类型
/**
* 如果参数是 ref,则返回其内部值,否则返回参数本身。这是 'val = isRef(val) ? val.value : val' 的语法糖。
*
* @example
* ```js
* function useFoo(x: number | Ref<number>) {
* const unwrapped = unref(x) // unwrapped 确保是 number 类型
* }
* ```
*
* @param ref - 需要转换为原始值的 ref 或普通值。
* @see {@link https://vuejs.org/api/reactivity-utilities.html#unref}
*/
export function unref<T>(ref: MaybeRef<T> | ComputedRef<T>): T { // 定义 unref 函数
return isRef(ref) ? ref.value : ref // 如果是 Ref,返回其值,否则返回原值
}
/**
* 规范化值/引用/获取器为值。
* 类似于 {@link unref()},但也会规范化获取器。
* 如果参数是获取器,则会调用它并返回其返回值。
*
* @example
* ```js
* toValue(1) // 1
* toValue(ref(1)) // 1
* toValue(() => 1) // 1
* ```
*
* @param source - 获取器、现有引用或非函数值。
* @see {@link https://vuejs.org/api/reactivity-utilities.html#tovalue}
*/
export function toValue<T>(source: MaybeRefOrGetter<T>): T { // 定义 toValue 函数
return isFunction(source) ? source() : unref(source) // 如果是函数执行并返回结果,否则返回 unref 结果
}
// 定义浅层解包的代理处理程序
const shallowUnwrapHandlers: ProxyHandler<any> = {
get: (target, key, receiver) => // 获取属性的处理
key === ReactiveFlags.RAW // 如果请求 raw 属性
? target // 直接返回目标
: unref(Reflect.get(target, key, receiver)), // 否则返回解包的值
set: (target, key, value, receiver) => { // 设置属性的处理
const oldValue = target[key] // 获取旧值
if (isRef(oldValue) && !isRef(value)) { // 如果旧值是 ref 且新值不是
oldValue.value = value // 更新旧值的 value
return true // 返回成功
} else {
return Reflect.set(target, key, value, receiver) // 否则正常设置
}
},
}
/**
* 返回一个代理对象,该对象对给定对象的属性进行浅层解包。
* 如果对象已是响应式的,则直接返回。
* 如果不是,则创建一个新的响应式代理。
*
* @param objectWithRefs - 已经是响应式对象或包含引用的简单对象。
*/
export function proxyRefs<T extends object>( // 定义 proxyRefs 函数
objectWithRefs: T,
): ShallowUnwrapRef<T> { // 返回包含解包引用的对象类型
return isReactive(objectWithRefs) // 如果对象已是响应式,则直接返回
? objectWithRefs
: new Proxy(objectWithRefs, shallowUnwrapHandlers) // 否则返回新的代理对象
}
export type CustomRefFactory<T> = ( // 定义自定义引用工厂类型
track: () => void, // 追踪函数
trigger: () => void, // 触发函数
) => {
get: () => T // 获取函数
set: (value: T) => void // 设置函数
}
class CustomRefImpl<T> { // 自定义引用实现类
public dep: Dep // 依赖管理
private readonly _get: ReturnType<CustomRefFactory<T>>['get'] // 获取函数
private readonly _set: ReturnType<CustomRefFactory<T>>['set'] // 设置函数
public readonly [ReactiveFlags.IS_REF] = true // 标记该对象是一个 Ref
public _value: T = undefined! // 实际值
constructor(factory: CustomRefFactory<T>) { // 构造函数
const dep = (this.dep = new Dep()) // 创建依赖实例
const { get, set } = factory(dep.track.bind(dep), dep.trigger.bind(dep)) // 从工厂获取获取器和设置器
this._get = get // 赋值获取器
this._set = set // 赋值设置器
}
get value() { // 获取值的 getter
return (this._value = this._get()) // 从获取器获取值
}
set value(newVal) { // 设置值的 setter
this._set(newVal) // 调用设置器
}
}
/**
* 创建一个自定义引用,显式控制其依赖追踪和更新触发。
*
* @param factory - 收到 `track` 和 `trigger` 回调的函数。
* @see {@link https://vuejs.org/api/reactivity-advanced.html#customref}
*/
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> { // 自定义引用工厂函数
return new CustomRefImpl(factory) as any // 返回自定义引用实现
}
export type ToRefs<T = any> = { // 定义将对象转换为引用的类型
[K in keyof T]: ToRef<T[K]> // 遍历对象的每个属性并转换为 ToRef 类型
}
/**
* 将响应式对象转换为一个普通对象,其中每个属性都是指向原始对象对应属性的 ref。
* 每个单独的 ref 是使用 {@link toRef()} 创建的。
*
* @param object - 要转换的响应式对象。
* @see {@link https://vuejs.org/api/reactivity-utilities.html#torefs}
*/
export function toRefs<T extends object>(object: T): ToRefs<T> { // 定义 toRefs 函数
if (__DEV__ && !isProxy(object)) { // 如果是开发环境且对象不是代理
warn(`toRefs() expects a reactive object but received a plain one.`) // 输出警告
}
const ret: any = isArray(object) ? new Array(object.length) : {} // 根据对象类型创建返回对象
for (const key in object) { // 遍历对象的每个属性
ret[key] = propertyToRef(object, key) // 将每个属性转换为 ref
}
return ret // 返回转换后的对象
}
class ObjectRefImpl<T extends object, K extends keyof T> { // 对象引用实现类
public readonly [ReactiveFlags.IS_REF] = true // 标记该对象是一个 Ref
public _value: T[K] = undefined! // 实际值
constructor( // 构造函数
private readonly _object: T, // 目标对象
private readonly _key: K, // 键
private readonly _defaultValue?: T[K], // 默认值
) {}
get value() { // 获取值的 getter
const val = this._object[this._key] // 获取目标对象中对应键的值
return (this._value = val === undefined ? this._defaultValue! : val) // 返回值或默认值
}
set value(newVal) { // 设置值的 setter
this._object[this._key] = newVal // 更新目标对象中的值
}
get dep(): Dep | undefined { // 获取依赖的方法
return getDepFromReactive(toRaw(this._object), this._key) // 获取依赖
}
}
class GetterRefImpl<T> { // Getter 引用实现类
public readonly [ReactiveFlags.IS_REF] = true // 标记该对象是一个 Ref
public readonly [ReactiveFlags.IS_READONLY] = true // 标记为只读
public _value: T = undefined! // 实际值
constructor(private readonly _getter: () => T) {} // 构造函数,接收一个获取器
get value() { // 获取值的 getter
return (this._value = this._getter()) // 执行获取器并返回值
}
}
export type ToRef<T> = IfAny<T, Ref<T>, [T] extends [Ref] ? T : Ref<T>> // 定义 ToRef 类型
/**
* 用于规范化值/引用/获取器为引用。
*
* @example
* ```js
* toRef(existingRef) // 返回现有的引用
* toRef(() => props.foo) // 创建一个访问 getter 的引用
* toRef(1) // 创建一个普通引用
* ```
*
* 还可以用于创建源响应式对象上某个属性的引用。
* 创建的引用与源属性同步:修改源属性会更新引用,反之亦然。
*
* @example
* ```js
* const state = reactive({
* foo: 1,
* bar: 2
* })
*
* const fooRef = toRef(state, 'foo') // 创建 foo 的引用
*
* fooRef.value++ // 修改引用会更新源
* console.log(state.foo) // 2
*
* state.foo++ // 修改源也更新引用
* console.log(fooRef.value) // 3
* ```
*
* @param source - 获取器、现有的引用、非函数值或
* 响应式对象以创建属性引用。
* @param [key] - (可选)响应式对象中的属性名。
* @see {@link https://vuejs.org/api/reactivity-utilities.html#toref}
*/
export function toRef<T>( // 定义 toRef 函数
value: T, // 函数重载
): T extends () => infer R
? Readonly<Ref<R>> // 如果是获取器,返回只读引用
: T extends Ref
? T // 如果已是 Ref,直接返回
: Ref<UnwrapRef<T>> // 否则创建一个新的引用
export function toRef<T extends object, K extends keyof T>( // 函数重载
object: T, // 如果是对象
key: K, // 目标键
): ToRef<T[K]> // 返回对应属性的引用
export function toRef<T extends object, K extends keyof T>( // 函数重载
object: T, // 目标对象
key: K, // 目标键
defaultValue: T[K], // 默认值
): ToRef<Exclude<T[K], undefined>> // 返回对应属性的引用或默认值
export function toRef( // 函数实现
source: Record<string, any> | MaybeRef, // 可能是普通对象或引用
key?: string, // 可选键
defaultValue?: unknown, // 可选默认值
): Ref { // 返回引用
if (isRef(source)) { // 如果源已经是引用,直接返回
return source
} else if (isFunction(source)) { // 如果源是函数,返回 GetterRefImpl
return new GetterRefImpl(source) as any
} else if (isObject(source) && arguments.length > 1) { // 如果是对象并且提供了键
return propertyToRef(source, key!, defaultValue) // 获取属性引用
} else {
return ref(source) // 否则创建新的 ref
}
}
function propertyToRef( // 处理对象属性转换为引用的辅助函数
source: Record<string, any>, // 源对象
key: string, // 属性键
defaultValue?: unknown, // 默认值
) {
const val = source[key] // 获取对象属性值
return isRef(val) // 如果属性值是引用,直接返回
? val
: (new ObjectRefImpl(source, key, defaultValue) as any) // 否则返回新创建的 ObjectRefImpl
}
/**
* 这是一个特殊的导出接口,用于其他包声明应跳过引用解包的附加类型。
* 例如 \@vue/runtime-dom 可以这样声明它:
*
* ``` ts
* declare module '@vue/reactivity' {
* export interface RefUnwrapBailTypes {
* runtimeDOMBailTypes: Node | Window
* }
* }
* ```
*/
export interface RefUnwrapBailTypes {} // 声明跳过解包的类型接口
export type ShallowUnwrapRef<T> = { // 定义浅层解包引用的类型
[K in keyof T]: DistributeRef<T[K]> // 遍历属性并解包
}
type DistributeRef<T> = T extends Ref<infer V, unknown> ? V : T // 判断是否为 Ref 并解包
export type UnwrapRef<T> = // 定义解包引用的类型
T extends ShallowRef<infer V, unknown>
? V // 如果是浅层引用,返回值
: T extends Ref<infer V, unknown>
? UnwrapRefSimple<V> // 如果是普通引用,递归解包
: UnwrapRefSimple<T> // 否则直接解包
export type UnwrapRefSimple<T> = T extends // 定义简单解包的类型
| Builtin // 如果是内置类型
| Ref // 如果是 Ref 类型
| RefUnwrapBailTypes[keyof RefUnwrapBailTypes] // 如果是 RefUnwrapBailTypes 类型
| { [RawSymbol]?: true } // 或含有 RawSymbol 的对象
? T // 直接返回
: T extends Map<infer K, infer V> // 如果是 Map 类型
? Map<K, UnwrapRefSimple<V>> & UnwrapRef<Omit<T, keyof Map<any, any>>> // 解包 Map 的值并递归解包其他属性
: T extends WeakMap<infer K, infer V> // 如果是 WeakMap 类型
? WeakMap<K, UnwrapRefSimple<V>> & // 解包 WeakMap 的值
UnwrapRef<Omit<T, keyof WeakMap<any, any>>> // 递归解包其他属性
: T extends Set<infer V> // 如果是 Set 类型
? Set<UnwrapRefSimple<V>> & UnwrapRef<Omit<T, keyof Set<any>>> // 解包 Set 的值
: T extends WeakSet<infer V> // 如果是 WeakSet 类型
? WeakSet<UnwrapRefSimple<V>> & UnwrapRef<Omit<T, keyof WeakSet<any>>> // 解包 WeakSet 的值
: T extends ReadonlyArray<any> // 如果是只读数组类型
? { [K in keyof T]: UnwrapRefSimple<T[K]> } // 解包数组中每一项
: T extends object & { [ShallowReactiveMarker]?: never } // 如果是对象并且没有浅层标记
? {
[P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]> // 遍历属性,正常解包
}
: T // 默认返回类型
- Ref 接口
Ref 是一种用于创建响应式引用的接口。它通过 value 属性来获取和设置内部值。当内部值发生变化时,依赖它的计算或组件会 automatisch 重新渲染。
- isRef 函数
isRef 函数用于检查一个值是否是 Ref 对象。它返回一个布尔值,表示传入的值是否为 Ref 类型。
- ref 函数
ref 函数用于创建一个响应式引用。它接收一个初始值,如果这个值是一个普通值(非 Ref),则会返回一个 Ref 对象,内部包含这个值。
- shallowRef 函数
shallowRef 是 ref 的一个变体,它用于创建一个浅响应式引用。浅响应式引用不会深入观察内部对象的变化,只会监视对 value 属性的替换,而不是内部字段的变化。
- triggerRef 函数
triggerRef 函数用于手动触发依赖于某个 shallowRef 的副作用。这通常用于在深层次操作后,强制更新依赖于这个引用的效果。
- toRef 和 toRefs 函数
toRef 函数用于创建一个 Ref,它与目标值保持同步。可以直接将普通值、另一个 Ref 或者一个带有 getter 的函数传入。 toRefs 函数将一个响应式对象的每个属性转换为一个 Ref 对象,方便在模板中使用。 7. customRef 函数
customRef 函数允许用户创建一个自定义的引用,能够自由控制其依赖追踪和更新触发的逻辑。用户需要提供一个工厂函数,该函数接收两个回调 track 和 trigger。
- 代理和解包
proxyRefs 函数返回一个代理对象,可以对其中的 Ref 属性进行解包,简化访问过程。 unref 和 toValue 函数用于将 Ref 解包为其内部值,简化获取值的操作。 9. 类型系统
代码中使用 TypeScript 来为不同的 API 和数据结构定义类型。例如,MaybeRef 表示可以是普通值、Ref 或 ShallowRef 的值。