【vue3源码】ref源码

1 ref 的底层逻辑是什么,具体是如何实现的

1.1 createRef 函数:ref 的调用

本篇文章 vue3 的版本为 3.5.13 ,ref 源码地址 core\packages\reactivity\src\ref.ts

// ref使用,调用createRef函数
export function ref(value?: unknown) {
  return createRef(value, false);
}

// createRef
function createRef(rawValue: unknown, shallow: boolean) {
  // 通过 isRef 判断原始值rawValue是否为 ref对象
  // 如果是 ref对象 ,则直接返回原始值
  if (isRef(rawValue)) {
    return rawValue;
  }
  // 如果不是 ref对象 ,返回 RefImpl包装类
  return new RefImpl(rawValue, shallow);
}

1.2 isRef 函数:判断原始值是否为 ref 对象

// isRef
export function isRef(r: any): r is Ref {
// isRef的实现,是通过 内部标识位ReactiveFlags 来判断
  return r ? r[ReactiveFlags.IS_REF] === true : false
}

// ReactiveFlags 标识位枚举
export enum ReactiveFlags {
  SKIP = '__v_skip',
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly',
  IS_SHALLOW = '__v_isShallow',
  RAW = '__v_raw',
  IS_REF = '__v_isRef',
}

1.3 RefImpl 包装类:ref 的底层逻辑

// RefImpl 包装类
class RefImpl<T = any> {
  _value: T
  private _rawValue: T

	// Dep 类是 Vue 3 响应式系统中用于管理依赖关系的核心组件,
	// 它通过双向链表和版本控制机制,确保依赖项的变化能够准确、高效地通知到所有订阅者。
  dep: Dep = new Dep()

	// 只读属性 IS_REF 内部判断是否为ref对象的标识位
  public readonly [ReactiveFlags.IS_REF] = true
  public readonly [ReactiveFlags.IS_SHALLOW]: boolean = false

  constructor(value: T, isShallow: boolean) {
		// 非浅比较 用 toRaw() 处理为原始值。toRaw源码见 1.4
    this._rawValue = isShallow ? value : toRaw(value)
		// 非浅比较 用 toReactive() 处理为响应式。toReactive源码见 1.4
    this._value = isShallow ? value : toReactive(value)
    this[ReactiveFlags.IS_SHALLOW] = isShallow
  }

	// getter 收集依赖
  get value() {
		// 在开发模式下,调用 dep.track 方法,传递目标对象、操作类型和键值,以跟踪依赖关系。
    if (__DEV__) {
      this.dep.track({
        target: this,
        type: TrackOpTypes.GET,
        key: 'value',
      })
    } else {
			// 在生产模式下,直接调用 dep.track 方法。开启依赖收集
      this.dep.track()
    }
    return this._value
  }

	// setter 触发依赖更新
  set value(newValue) {
    const oldValue = this._rawValue

		// 通过 标识位ReactiveFlags 判断是否直接使用新值
    const useDirectValue =
      this[ReactiveFlags.IS_SHALLOW] ||
      isShallow(newValue) ||
      isReadonly(newValue)

		// 通过 useDirectValue 判断是否直接使用新值 还是解包新值返回原始值
    newValue = useDirectValue ? newValue : toRaw(newValue)

		// 检查新值与旧值是否发生变化。
    if (hasChanged(newValue, oldValue)) {
      this._rawValue = newValue
      this._value = useDirectValue ? newValue : toReactive(newValue)
			// 在开发模式下,调用 dep.trigger 方法,传递目标对象、操作类型、键值、新值和旧值,以触发依赖更新。
      if (__DEV__) {
        this.dep.trigger({
          target: this,
          type: TriggerOpTypes.SET,
          key: 'value',
          newValue,
          oldValue,
        })
      } else {
			// 在生产模式下,直接调用 dep.trigger 方法。触发依赖更新
        this.dep.trigger()
      }
    }
  }
}

1.4 RefImpl 包装类 所使用到的工具函数

// 转为响应式对象
// 值为对象类型,则使用 Proxy 代理。否则返回原始值
export const toReactive = <T extends unknown>(value: T): T =>
  isObject(value) ? reactive(value) : value

// 解包 vue创建的代理 为 原始对象。
// toRaw() 可以返回由 reactive()、readonly()、shallowReactive() 或者 shallowReadonly() 创建的代理对应的原始对象。
export function toRaw<T>(observed: T): T {
	// 通过判断代理对象的 标识位ReactiveFlags 来判断是否需要解包
  const raw = observed && (observed as Target)[ReactiveFlags.RAW]
	// 递归解包代理对象
  return raw ? toRaw(raw) : observed
}

// 判断是否为只读对象。通过 标识位ReactiveFlags 判断
export function isReadonly(value: unknown): boolean {
  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}

// 判断是否为浅比较。通过 标识位ReactiveFlags 判断
export function isShallow(value: unknown): boolean {
  return !!(value && (value as Target)[ReactiveFlags.IS_SHALLOW])
}

// 判断新旧值是否变更。
export const hasChanged = (value: any, oldValue: any): boolean =>
  !Object.is(value, oldValue)


// 因为这里不讨论具体的响应式实现原理,dep类的track 和 trigger 这里不展开描述

1.5 面试回答

  1. 通过 isRef 判断是否为 ref 对象。是 ref 对象则直接返回,不是 ref 对象则使用 RefImpl 包装类
  2. ReactiveFlags.IS_REF 带上标识位,后续可以通过标识位判断代理值是否为 ref 对象。
  3. RefImpl 包装类 构造器中,浅比较直接返回值,非浅比较则使用 toReactive 转换响应式。当值为对象类型则直接调用 reactive() 使用 Proxy 包装
  4. 通过 RefImpl 包装类 的包装类的 getter 和 setter 来触发依赖收集和派发更新流程。

2 ref 底层是否会使用 reactive 处理数据

2.1 源码分析

通过 RefImpl 包装类 源码 中构建器 对 传入值的处理可知,当没有开启浅比较的时候

  • 如果 ref 代理的值是对象类型,则直接调用 reactive 处理
  • 如果 ref 代理的值是基础类型,则直接返回原始值。
	// RefImpl 包装类 构造器。详细见 1.3
	constructor(value: T, isShallow: boolean) {
		// 非浅比较 用 toRaw() 处理原始值。toRaw源码见 1.4
		this._rawValue = isShallow ? value : toRaw(value)
		// 非浅比较 用 toReactive() 处理值。toReactive源码见 1.4
		this._value = isShallow ? value : toReactive(value)
		this[ReactiveFlags.IS_SHALLOW] = isShallow
	}

	// 响应式处理。详细见 1.4
	export const toReactive = <T extends unknown>(value: T): T =>
  isObject(value) ? reactive(value) : value

2.2 面试回答

ref 底层使用 reactive 处理数据 需要两个前置条件,不开启浅比较和 ref 代理的值为对象类型。

3 为什么已经有了 reactive 还需要在设计一个 ref 呢?

reactive 底层使用了 Proxy 代理值,这个值只能是任何类型的对象,包括原生的数组、函数甚至是另一个代理对象。但是无法处理基本类型。所以处理基础类型需要包裹一层

4 为什么 ref 数据必须要有个 value 属性,访问 ref 数据必须要通过.value 的方式呢?

通过对 RefImpl 包装类可知,底层通过 getter 和 setter 来进行属性劫持来触发更新。所以访问和修改时必须使用 value。

// RefImpl 包装类 详细见1.3
class RefImpl<T = any> {
  // 管理依赖更新
  dep: Dep = new Dep();
  constructor(value: T, isShallow: boolean) {}
  // getter 收集依赖
  get value() {}
  // setter 触发依赖更新
  set value(newValue) {}
}
posted @ 2025-03-13 17:23  wanglei1900  阅读(31)  评论(0)    收藏  举报