本文导读

ZuB1M1H.png

我们的代码虽然已经可以运作,但 RefImpl 这个类同时处理了数据存储和链表管理,不易于扩展,所以需要调整代码结构。尽管前几章的代码已经能正常工作,但它存在一个很大的问题:RefImpl 这个类承担了太多的责任。

它既要负责存储数值 (_value),又要管理一整套复杂的链表操作。

这种设计违反了软件工程中的 “单一职责原则 (Single Responsibility Principle)” ,会导致代码难以阅读、维护和扩展。

ref.ts

首先,我们把 RefImpl 中的链表操作抽离出来,创建两个独立的函数:

  • trackRef:收集依赖
  • triggerRef:触发更新
class RefImpl {
  _value;
  [ReactiveFlags.IS_REF] = true

  subs: Link 
  subsTail: Link

  constructor(value){
    this._value = value
  }

  get value(){ 
    if(activeSub){
      trackRef(this)
    }
    return this._value
  }

  set value(newValue){ 
    this._value = newValue
    triggerRef(this)
  }
}
/*
 * 这里的 dep 是 ref
 * 收集依赖,建立 ref 和 effect 之间的链表关系
 */
export function trackRef(dep){
      const newLink = {
        sub: activeSub,
        nextSub: undefined,
        prevSub: undefined
      }
  
      if(dep.subsTail){
        dep.subsTail.nextSub = newLink
        newLink.prevSub = dep.subsTail
        dep.subsTail = newLink
      } else { 
        dep.subs = newLink
        dep.subsTail = newLink
      }
}

/*
 * 触发 ref 关联的 effect,使其重新执行
 */
export function triggerRef(dep){
    let link = dep.subs
    let queuedEffect = []

    while (link){
      queuedEffect.push(link.sub)
      link = link.nextSub
    }
    queuedEffect.forEach(effect => effect())
}

接着新建一个 system.ts 文件,用于存放链表相关逻辑,进行再次拆分:

  • trackRef:作为收集依赖的入口函数,判断是否存在 activeSub,如果存在,则建立链表关系。

    • effect(fn) 在调用 fn() 前会把自己设为 activeSub,在 fn() 结束后清空。所以我们使用 activeSub 来判断它是否为当前正在执行的 effect(fn)
  • triggerRef:作为触发更新的入口函数,需要找到并通知曾经订阅过这个 dep 的所有 effect。因此我们判断,如果 dep 存在 subs,它就触发更新。

  • dep (dependency) = 被依赖的对象(如 refreactive

  • sub (subscriber) = 订阅者(如 effectwatch

 // system.ts
import { ReactiveEffect } from './effect'

export interface Link {
  sub: ReactiveEffect
  nextSub: Link
  prevSub: Link
}

/* * 建立链表关系
 * dep 是依赖项,例如 ref/computed/reactive
 * sub 是订阅者,例如 effect
 * 当依赖项(ref)变化时,需要通知订阅者(effect)
 */
export function link(dep, sub){
    // 建立新的链表节点
    const newLink: Link = {
      sub,              // 指向目前的订阅者 (activeSub)
      nextSub: undefined, // 指向下一个节点 (初始化为空)
      prevSub: undefined  // 指向前一个节点 (初始化为空)
    }

    // 如果 dep 已经有尾端订阅者 (代表链表不是空的)
    if(dep.subsTail){
      // 把尾端节点的 next 指向新的节点
      dep.subsTail.nextSub = newLink
      // 新节点的 prev 指向原本的尾端
      newLink.prevSub = dep.subsTail
      // 更新 dep 的尾端指标为新节点
      dep.subsTail = newLink
    } else { 
      // 如果 dep 还没有任何订阅者 (第一次建立链表)
      dep.subs = newLink       // 链表的头指向新节点
      dep.subsTail = newLink   // 链表的尾也指向新节点
    }
}

/* * 传播更新的函数
 */
export function propagate(subs){
  let link = subs
  let queuedEffect = []

  while (link){
    queuedEffect.push(link.sub)
    link = link.nextSub
  }

  queuedEffect.forEach(effect => effect.run())
}
// ref.ts
import { activeSub, ReactiveEffect } from './effect'
import { Link, link, propagate } from './system'

enum ReactiveFlags {
  IS_REF = '__v_isRef'
}

class RefImpl {
  _value;
  [ReactiveFlags.IS_REF] = true

  subs: Link
  subsTail: Link
  constructor(value){
    this._value = value
  }

  get value(){ 
    if(activeSub){
      trackRef(this)
    }
    return this._value
  }

  set value(newValue){ 
    this._value = newValue
    triggerRef(this)
  }
}

export function ref(value){
   return new RefImpl(value)
}

export function isRef(value){
  return !!(value && value[ReactiveFlags.IS_REF])
}

/*
 * 这里的 dep 是 ref
 * 收集依赖,建立 ref 和 effect 之间的链表关系
 */
export function trackRef(dep){
  if(activeSub){
    link(dep, activeSub)
  }
}

/*
 * 触发 ref 关联的 effect,使其重新执行
 */
export function triggerRef(dep){
    if(dep.subs){
      propagate(dep.subs)
    }
} 

Effect.ts

// effect.ts
// 用于保存当前正在执行的 effect
export let activeSub;

export function effect(fn){
  activeSub = fn
  activeSub()
  activeSub = undefined
}

我们将 effect 函数重构为一个类,并给它一个 run 方法:

// effect.ts
import { Link } from './system'

export let activeSub: ReactiveEffect;

export class ReactiveEffect {
  constructor(public fn: Function) {}

  run(){
      // 每次执行 fn 之前,把 this 实例放到 activeSub 上
      activeSub = this
    try {
      return this.fn()
    } finally {
      // 执行完毕后,清空 activeSub
      activeSub = undefined
    }
  }
}

export function effect(fn){
  const e = new ReactiveEffect(fn)
  e.run()
}

为什么将 effect 重构为 ReactiveEffect 类?

主要有三大好处:

  1. 状态封装effect 本身其实是有状态的(例如它依赖了谁、是否正在执行等)。类是封装这些状态和相关行为的最好办法。
  2. 功能扩展effect 成为一个类后,当有需要时,我们可以轻松地为它新增更多方法,比如 run() 就是一个很好的例子。
  3. 更好的 this 指向:在 run() 方法中,activeSub 被赋值为 this (也就是 ReactiveEffect 的实例),方便后续我们从 effect 实例上获取更多需要的信息。

也正因如此,effect 从函数变成了对象,所以我们要调整一下调用方式。

// system.ts
import { ReactiveEffect } from './effect'

export interface Link {
  // 由于调整,effect 是对象
  sub: ReactiveEffect
  nextSub: Link
  prevSub: Link
}
//...
//...
export function propagate(subs){
  //...
  // effect 变成了对象,改调用其 run 方法
  queuedEffect.forEach(effect => effect.run())
}

回顾我们今天完成的事:我们将 RefImpl 中复杂的依赖追踪逻辑,拆分到了独立的 system.ts 模块,并且把 effect 重构成一个更易于维护的 ReactiveEffect 类。

现在,我们的响应式核心由三部分组成:RefImpl (负责数据内容)、ReactiveEffect (负责副作用)、以及 system.ts (连接它们的桥梁)。

明天我们就可以开始处理 effect 相关的其他问题了。


想了解更多 Vue 的相关知识,抖音、B站搜索我师父「远方os」,一起跟日安当同学。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]