likes
comments
collection
share

Vue3硬核源码解析系列(6) 80行代码 实现mini版ref

作者站长头像
站长
· 阅读数 18

专栏前言

​ 在上一节,我们完成了vue3ref核心源码解读,其实基础类型的ref的核心逻辑还是非常简单的,所以在我们的简易版源码环节,我们直接切入基础类型,复杂类型仅做支持,不做讲解。

注:单基础类型场景的ref源码,几乎可以说是整个vue3源码中最简单的一部分,所以这一节的学习难度是最小的

mini版vue3仓库地址,还请大家不要吝啬star,下次不迷路~

仅保留最核心逻辑,极大减低阅读难度,80行代码实现vue3 ref,让我们直接进入源码实现环节!

逻辑图(基础类型)

完整版ref逻辑图,请看 Vue3硬核源码解析系列(5) ref源码解析

Vue3硬核源码解析系列(6) 80行代码 实现mini版ref

具体逻辑

如同逻辑图所示,我们简易版源码的具体实现也从 初始化 依赖收集 依赖触发三个角度来进行实现

初始化

ref的初始化非常简单,逻辑流程如下

  1. 判断传入对象是否已经是ref,如果是,这直接返回,如果不是,则继续运行代码
  2. ref的本质就是一个Class RefImpl
  3. 初始化RefImpl的时候,将ref的参数保存到_value,同时将参数的原始值保存到_rawValue
  4. 通过get value,实现ref.value的访问
  5. 使用set value,实现ref.value = xx的更新逻辑

确定实现逻辑的同时,我们也仿照vue3的源码结构开始输出吧~

/**
 * 入口函数
 */
export function ref(value?: unknown) {
  return createRef(value, false)
}

function createRef(rawValue: unknown, shallow: boolean) {
  // 判断是否已经是ref,如果是直接返回其本身
  if (isRef(rawValue)) {
    return rawValue
  }
  // ref本质上就是RefImpl的实例
  return new RefImpl(rawValue, shallow)
}

class RefImpl<T = any> {
  private _value: T // ref每次读取与返回的属性
  private _rawValue: T // ref中value的原始属性
  public dep: Dep | undefined // 当前ref相关effect
  public readonly __v_isRef: boolean = true // 标记__v_isRef为true,以后将无法在通过isRef()的判断
  constructor(value: T, public readonly __v_isShallow: boolean) {

    this._rawValue = value     // 赋值原始值
    // ref API中 __v_isShallow,一定为false (__v_isShallow 表示是否浅层代理)
    // value是基础类型,则toReactive返回原值,value是复杂类型,则toReactive会将其处理成为reactive(proxy)再返回,这就意味着,此时的value是一个proxy
    this._value = __v_isShallow ? value : toReactive(value)
  }
  // 实现ref.value能力
  get value() {
    // 配合effect阶段保存的activeEffect,将依赖收集到this.dep中(依赖收集)
    trackRefValue(this)
    // 返回最新value
    return this._value
  }
  // 实现ref.value = xx能力
  set value(newVal) {
    // 判断当前set的value是否存在变化, 有变化则进入if
    if (hasChange(newVal, this._rawValue)) {
      // 保存最新的参数原始值,便于下次hasChange判断
      this._rawValue = newVal
      // 如果value是基础类型, 则toReactive返回value本身,否则返回通过toReactive生成的proxy
      this._value = toReactive(newVal)
      // 触发get阶段收集在this.dep中的依赖(依赖触发)
      triggerRefValue(this)
    }
  }
}

依赖收集

ref = Class RefImpl

​ 经过我们上一章的ref源码分析我们可以了解到,ref的依赖收集,并不是依赖WeakMap进行完成,而是其自行完成依赖收集,收集在自身classdep中,逻辑大概是这样的

  1. 每次触发refget的时候,都会执行一次trackRefValue(trackRefValue的作用是完成依赖收集)
  2. 每次执行effect的时候,都会将effect本身保存到变量activeEffect中(具体请看Vue3硬核源码解析系列(3) reactive + effect源码解析
  3. 如果RefImpldep不存在,则说明是第一次进行依赖收集,将通过createDepRefImpl.dep赋值为Set
  4. activeEffect,也就是当前正在运行的effectpushRefImpldep中,ref完成依赖收集

明确了逻辑之后,我们依旧结合vue3的源码结构,来完成ref依赖收集的代码输出。

get value() {
  trackRefValue(this)
  return this._value
}

/**
 * ref 依赖收集
 */
export function trackRefValue(ref: RefImpl) {
  // 判断当前是否存在需要收集的依赖
  if (activeEffect) {
    // 判断RefImpl的实例中的dep是否被初始化过
    if (!ref.dep) {
      // 如果没有, 则赋值为Set
      ref.dep = createDep()
    }
    // 将当前effect收集到当前RefImpl实例的dep中, 完成依赖收集
    trackEffects(ref.dep)
  }
}

/**
 *
 * @param dep
 */
export function trackEffects(dep: Dep) {
  dep.add(activeEffect!)
}

依赖触发

若干时间后,refvalue被更新,触发RefImplset value,在更新value的同时,也会执行其内部的triggerRefValue,开始依赖触发逻辑

  1. 获取到当前ref,也就是class RefImpl本身的dep
  2. 循环dep中存储的所有effect,并执行其fn,完成依赖触发。
/**
 * ref 依赖触发
 */
export function triggerRefValue(ref: RefImpl) {
  // 当前当前RefImpl实例中是否存在收集的依赖
  if (ref.dep) {
    // 触发依赖
    triggerEffects(ref.dep)
  }
}

/**
 * 处理所有待触发依赖
 */
export function triggerEffects(dep: Dep) {
  // const effects = isArray(dep) ? dep : [...dep]
  const effects = [...dep]
  for (const effect of effects) {
    triggerEffect(effect)
  }
}

/**
 * 触发执行依赖
 */
function triggerEffect(effect: ReactiveEffect) {
  effect.run()
}

到此为止,我们的ref就具备响应式的能力了,是不是很简单~

小结

​ 这时候肯定有同学要说了,你这ref不保熟啊,仅支持基础类型,不支持复杂类型啊,这不是阉割版ref吗?

​ 这里必须澄清一下,虽然简易版ref 100行代码不到,但是他是支持复杂类型的响应式的,因为复杂类型的响应式是依赖reactive进行完成的,不过reactive的源码解读,并不是本文的重点,所以,这里就跳过了,有兴趣的同学,请看这里Vue3硬核源码解析系列(3) reactive + effect源码解析,了解reactive的响应式实现,再看Vue3硬核源码解析系列(5)ref源码解析,了解复杂类型场景下的源码执行逻辑吧。

​ 最后,建议大家clone源码到本地实际运行一下,静下心来一步一步调试,将简易版逻辑弄明白,有兴趣的可以在看看正式的vue3源码,在简历上留下浓墨重彩的一笔~