Vue3源码分析(二)- ref 原理

在 Vue3 中,因为 reactive 创建的响应式对象是通过 Proxy 来实现的,所以传入数据只能是引用类型,而基础类型无法实现,所以 ref 对象是对 reactive 不支持的数据的一个补充,将基础类型包装成 Ref 对象从而实现响应式

辅助函数

isRef

TS 复制代码
// packages/reactivity/src/ref.ts

export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
export function isRef(r: any): r is Ref { // is 是类型谓语作为运行时的类型检查
// 判断是否为Ref类型
  return !!(r && r.__v_isRef === true) // !! 表示将类型转成布尔类型
}

shallowRef

  • 浅层响应式:表示只有对象本身被响应式跟踪,如果对象的属性值是对象,则那些属性不会被响应式跟踪
  • 深层响应式:表示对象及其嵌套属性都会被响应式跟踪
TS 复制代码
// packages/reactivity/src/ref.ts

export function shallowRef<T = any>(): ShallowRef<T | undefined>
export function shallowRef(value?: unknown) {
  return createRef(value, true) // 表示将对象标记为浅层响应式
}

toRaw

TS 复制代码
// packages/reactivity/src/ref.ts

// 获取经过响应式处理后的原始值
export function toRaw<T>(observed: T): T {
  const raw = observed && (observed as Target)[ReactiveFlags.RAW] // 被响应式处理后的对象会被标记为ReactiveFlags.RAW,可以存储原始值
  return raw ? toRaw(raw) : observed // 递归调用toRaw函数,获取更深层次的原始值
}

createRef

TS 复制代码
// packages/reactivity/src/ref.ts

export function ref(value?: unknown) {
  return createRef(value, false) // 默认将对象标记为深层响应式
}

function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
  // 如果已经是Ref类型的值,直接返回
    return rawValue
  }
  return new RefImpl(rawValue, shallow) // 创建实例
}

RefImpl

TS 复制代码
// packages/reactivity/src/ref.ts

class RefImpl<T> {
    constructor(
        value: T,
        public readonly __v_isShallow: boolean, // 是否使用浅层响应式
     ) {
        this._rawValue = __v_isShallow ? value : toRaw(value) // 表示原始的值,即未处理的值
        this._value = __v_isShallow ? value : toReactive(value) // 表示经响应式处理后的值
    }
    
    get value() {
        trackRefValue(this) // 收集依赖
        return this._value
    }

    set value(newVal) {
        // useDirectValue用来判断是否直接使用新值
        // - this.__v_isShallow:表示当前引用是否为浅层响应式
        // - isShallow:表示新值是否为浅层响应式
        // - isReadonly:表示新值是否为只读的
        const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
        newVal = useDirectValue ? newVal : toRaw(newVal) // useDirectValue为false时可能新值已经是响应式对象,需要转成原始类型
        if (hasChanged(newVal, this._rawValue)) {
          this._rawValue = newVal
          this._value = useDirectValue ? newVal : toReactive(newVal)
          triggerRefValue(this, DirtyLevels.Dirty, newVal) // 触发依赖
        }
    }
}

trackRefValue

TS 复制代码
// packages/reactivity/src/ref.ts

export function trackRefValue(ref: RefBase<any>) {
  if (shouldTrack && activeEffect) {
  // 只有在需要时且处于激活状态才追踪引用的值
  // - 如果定义了Ref变量,但是没有任何地方使用到,此时就不需要收集依赖
  // - 对于一些改变数组长度的方法中,就不需要收集依赖,防止造成无限循环
    ref = toRaw(ref) // 确保追踪的是原始值而不是经过响应式处理后的值
    trackEffect(
      activeEffect,
      // 简化写法,等价写法见如下
      (ref.dep ??= createDep(
        () => (ref.dep = undefined),
        ref instanceof ComputedRefImpl ? ref : undefined,
      )),
      __DEV__
        ? {
            target: ref,
            type: TrackOpTypes.GET,
            key: 'value',
          }
        : void 0,
    )
  }
}
TS 复制代码
// 简化写法
(ref.dep ??= createDep(
     () => (ref.dep = undefined),
     ref instanceof ComputedRefImpl ? ref : undefined,
 )),
 
// 等价写法      
if (ref.dep === undefined || ref.dep === null) { 
    ref.dep = createDep(
        () => (ref.dep = undefined),
        ref instanceof ComputedRefImpl ? ref : undefined,
    ); 
}

triggerRefValue

TS 复制代码
// packages/reactivity/src/constants.ts

export enum DirtyLevels {
  NotDirty = 0, // 未脏化:表示值没有发生变化
  QueryingDirty = 1, // 查询脏化:表示引用在进行查询操作,通常处理一些异步操作
  MaybeDirty_ComputedSideEffect = 2, // 可能脏化-计算副作用:由计算引用的副作用触发
  MaybeDirty = 3, // 可能脏化:原因不明确
  Dirty = 4, // 脏化:需要通知依赖于该引用的其他地方进行相应的更新操作
}
TS 复制代码
// packages/reactivity/src/ref.ts

export function triggerRefValue(
  ref: RefBase<any>,
  dirtyLevel: DirtyLevels = DirtyLevels.Dirty, // 表示引用的脏状态,默认处于脏状态需要触发更新
  newVal?: any,
) {
  ref = toRaw(ref)
  const dep = ref.dep
  if (dep) {
    triggerEffects(
      dep,
      dirtyLevel,
      __DEV__
        ? {
            target: ref,
            type: TriggerOpTypes.SET,
            key: 'value',
            newValue: newVal,
          }
        : void 0,
    )
  }
}

类型嵌套

理想状态是直接通过 .value 获取值,而不是 .value.value.value.... 的形式,目的是让ref(ref(ref('123'))) 能够顺利推导出 ref 是 string 类型

UnwrapRef 类型

单层解包

运用 extends三元表达式对 ref 函数的返回值进行条件判断,如果 T 是 Ref 类型则直接返回,否则需要将 T 包装成 Ref 类型再返回

TS 复制代码
type UnwrapRef<T> = T extends Ref<infer R> ? R : T

// 若 infer R 中的 R 还是 Ref 类型?

递归解包

索引签名的形式,增加能够停止递归的条件,对 Ref 类型进行反解

TS 复制代码
type UnwrapRef<T> = {
  ref: T extends Ref<infer R> ? R : T
  other: T
}[T extends Ref ? 'ref' : 'other']

对象解包

Ts 复制代码
type UnwarpRef<T> = {
    ref: T extends Ref<infer R> ? R : T 
    // 注意这里 
    object: { [K in keyof T]: UnwarpRef<T[K]> }
    other: T 
}
    [T extends Ref
        ? 'ref' : T extends object
        ? 'object' 
        : 'other'
    ]

完整源码

  • 参数是简单类型直接返回
  • 参数是 Ref ,用 infer v 获取参数类型返回v
  • 参数是 object,遍历 object 将属性反解成简单类型
  • 参数是 Array,遍历 Array 保留数组元素中的类型
  • 参数是 Map、WeakMap、Set、WeakSet,返回对应类型,其中值被递归反解
TS 复制代码
// packages/reactivity/src/ref.ts

export type UnwrapRef<T> =
  T extends ShallowRef<infer V>
    ? V
    : T extends Ref<infer V>
      ? UnwrapRefSimple<V>
      : UnwrapRefSimple<T>

export type UnwrapRefSimple<T> = T extends
  | Function
  | BaseTypes
  | Ref
  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]
  | { [RawSymbol]?: true }
  ? T
  : T extends Map<infer K, infer V> 
    ? Map<K, UnwrapRefSimple<V>> & UnwrapRef<Omit<T, keyof Map<any, any>>> // Omit忽略,表示以某个类型为基础剔除某些属性,返回一个新的类型
    : T extends WeakMap<infer K, infer V>
      ? WeakMap<K, UnwrapRefSimple<V>> &
          UnwrapRef<Omit<T, keyof WeakMap<any, any>>>
      : T extends Set<infer V> 
        ? Set<UnwrapRefSimple<V>> & UnwrapRef<Omit<T, keyof Set<any>>>
        : T extends WeakSet<infer V>
          ? WeakSet<UnwrapRefSimple<V>> & UnwrapRef<Omit<T, keyof WeakSet<any>>>
          : 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
相关推荐
QGC二次开发2 分钟前
Vue3 : Pinia的性质与作用
前端·javascript·vue.js·typescript·前端框架·vue
想退休的搬砖人1 小时前
vue选项式写法项目案例(购物车)
前端·javascript·vue.js
啥子花道2 小时前
Vue3.4 中 v-model 双向数据绑定新玩法详解
前端·javascript·vue.js
清灵xmf2 小时前
揭开 Vue 3 中大量使用 ref 的隐藏危机
前端·javascript·vue.js·ref
学习路上的小刘2 小时前
vue h5 蓝牙连接 webBluetooth API
前端·javascript·vue.js
&白帝&2 小时前
vue3常用的组件间通信
前端·javascript·vue.js
冯宝宝^4 小时前
基于mongodb+flask(Python)+vue的实验室器材管理系统
vue.js·python·flask
cc蒲公英4 小时前
Vue2+vue-office/excel 实现在线加载Excel文件预览
前端·vue.js·excel
森叶4 小时前
Electron-vue asar 局部打包优化处理方案——绕开每次npm run build 超级慢的打包问题
vue.js·electron·npm
小小竹子5 小时前
前端vue-实现富文本组件
前端·vue.js·富文本