Vue3之ref 实现源码深度解读

ref基础使用

js 复制代码
import { ref } from 'vue';

// 创建响应式引用
const count = ref(0);

// 使用引用的值
console.log(count.value); // 0

// 更新引用值
count.value++;
console.log(count.value); // 1

// 通过函数改变值
function increment() {
  count.value++;
}
increment();
console.log(count.value); // 2
  • 定义响应式引用 :使用 ref 创建响应式的数据存储,例如 const count = ref(0)
  • 访问值 :使用 .value 属性访问或设置 ref 的内容。
  • 响应式特性ref 创建的对象是响应式的,任何对其 .value 的变化都将触发相关的更新和依赖。

ref 实现源码

js 复制代码
// 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 // 默认返回类型

1. Ref 接口

Ref 是一种用于创建响应式引用的接口。它通过 value 属性来获取和设置内部值。当内部值发生变化时,依赖它的计算或组件会 automatisch 重新渲染。

2. isRef 函数

isRef 函数用于检查一个值是否是 Ref 对象。它返回一个布尔值,表示传入的值是否为 Ref 类型。

3. ref 函数

ref 函数用于创建一个响应式引用。它接收一个初始值,如果这个值是一个普通值(非 Ref),则会返回一个 Ref 对象,内部包含这个值。

4. shallowRef 函数

shallowRefref 的一个变体,它用于创建一个浅响应式引用。浅响应式引用不会深入观察内部对象的变化,只会监视对 value 属性的替换,而不是内部字段的变化。

5. triggerRef 函数

triggerRef 函数用于手动触发依赖于某个 shallowRef 的副作用。这通常用于在深层次操作后,强制更新依赖于这个引用的效果。

6. toReftoRefs 函数

  • toRef 函数用于创建一个 Ref,它与目标值保持同步。可以直接将普通值、另一个 Ref 或者一个带有 getter 的函数传入。
  • toRefs 函数将一个响应式对象的每个属性转换为一个 Ref 对象,方便在模板中使用。

7. customRef 函数

customRef 函数允许用户创建一个自定义的引用,能够自由控制其依赖追踪和更新触发的逻辑。用户需要提供一个工厂函数,该函数接收两个回调 tracktrigger

8. 代理和解包

  • proxyRefs 函数返回一个代理对象,可以对其中的 Ref 属性进行解包,简化访问过程。
  • unreftoValue 函数用于将 Ref 解包为其内部值,简化获取值的操作。

9. 类型系统

代码中使用 TypeScript 来为不同的 API 和数据结构定义类型。例如,MaybeRef 表示可以是普通值、RefShallowRef 的值。

相关推荐
桂月二二20 分钟前
深入探讨 Vue.js 的动态组件渲染与性能优化
vue.js
疯狂的沙粒5 小时前
对React的高阶组件的理解?应用场景?
前端·javascript·react.js·前端框架
lryh_5 小时前
react 中使用ant 的 Table时警告:没有设置key
前端·react.js·前端框架
oil欧哟7 小时前
uniapp 小程序 textarea 层级穿透,聚焦光标位置错误怎么办?
vue.js·小程序·uni-app·uniapp
不修×蝙蝠7 小时前
vue(七) vue进阶
前端·javascript·vue.js·前端框架·vue·ssm·进阶
工业互联网专业13 小时前
基于springboot+vue的 嗨玩-旅游网站
java·vue.js·spring boot·毕业设计·源码·课程设计·旅游
码蜂窝编程官方13 小时前
【含开题报告+文档+PPT+源码】基于SpringBoot+Vue的医院挂号预约管理系统
java·vue.js·spring boot·后端·spring
GISer_Jing14 小时前
React中Element&Fiber对象、WorkInProgress双缓存、Reconcile&Render&Commit、第一次挂载过程详解
javascript·react.js·前端框架
72degrees14 小时前
vue2迁移至rsbuild
前端·javascript·vue.js
贵州晓智信息科技15 小时前
Threejs实现 区块链网络效应
前端·javascript·vue.js·ecmascript