Skip to content

问题背景

代码虽然能跑,但 RefImplget valueset value 混杂了过多职责,已经偏离“单一职责原则”。

将 ref 的依赖收集与更新触发拆分为独立模块,让结构更清晰、职责更明确,也更易维护。

ref.ts

ts

class RefImpl {
  _value;

  [ReactiveFlags.IS_REF] = true // 证明是一个 Ref

  /**
   * 订阅者链表的头节点
   */
  subs: Link

  /**
   * 订阅者链表的尾节点
   */
  subsTail: Link | undefined

  constructor(value) {
    this._value = value
  }

  get value() {
    // 收集依赖
    if (activeSub) {
      trackRef(this)
    }
    return this._value
  }

  set value(newValue) {
    // 触发更新
    this._value = newValue
    triggerRef(this)
  }
}
ts
export function trackRef(dep) {
  const newLink: Link = {
    sub: activeSub,
    nextSub: undefined,
    prevSub: undefined
  }

  /**
   * 链表关系关联
   *  1. 如果存在尾节点,往尾节点后面加
   *  2. 如果不存在尾节点,头节点和尾节点相同
   */
  if (dep.subsTail) {
    dep.subsTail.nextSub = newLink
    newLink.prevSub = dep.subsTail
    dep.subsTail = newLink
  } else {
    dep.subs = newLink
    dep.subsTail = newLink
  }
}

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

接着通过重构,将 trackReftriggerRefRefImpl 中抽离,放到独立模块中。

ts
// system.ts
import type {EffectFn} from './effect'

export interface Link {
  sub: EffectFn // 保存 effect
  nextSub: Link | undefined // 下一个节点
  prevSub: Link | undefined // 上一个节点
}

/**
 * 建立链表关系
 * @param dep 依赖项 ref reactive computed
 * @param sub 订阅者 effect
 */
export function link(dep, sub) {
  const newLink: Link = {
    sub,
    nextSub: undefined,
    prevSub: undefined
  }
  /**
   * 链表关系关联
   *  1. 如果存在尾节点,往尾节点后面加
   *  2. 如果不存在尾节点,头节点和尾节点相同
   */
  if (dep.subsTail) {
    dep.subsTail.nextSub = newLink
    newLink.prevSub = dep.subsTail
    dep.subsTail = newLink
  } else {
    dep.subs = newLink
    dep.subsTail = newLink
  }
}

/**
 * 传播更新的函数
 * @param subs
 */
export function propagate(subs) {
  let linkNode: Link | undefined = subs
  const queuedEffect: EffectFn[] = []
  while (linkNode) {
    queuedEffect.push(linkNode.sub)
    linkNode = linkNode.nextSub
  }

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

enum ReactiveFlags {
  IS_REF = '__v_isRef'
}

class RefImp {
  _value: T;
  [ReactiveFlags.IS_REF] = true // 证明是一个 Ref

  /**
   * 订阅者链表的头节点
   */
  subs: Link | undefined
  /**
   * 订阅者链表的尾节点
   */
  subsTail: Link | undefined

  constructor(value) {
    this._value = value
  }

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

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

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

/**
 * 收集依赖,简历链表和响应式之间的关系
 */
export function trackRef(dep) {
  if (activeSub) {
    link(dep, activeSub)
  }
}

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

effect.ts

ts
export let activeSub: ReactiveEffect | undefined

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

  run(): any {
    activeSub = this

    try {
      return this.fn()
    } finally {
      activeSub = undefined
    }
  }
}

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

为什么将 effect 设计为一个类

  1. 状态封装: effect 本身是有状态的(它依赖了谁、是否正在执行)。类更适合封装这类状态与行为。
  2. 功能扩展: effect 未来可能需要更多能力(如停止、调度等),用类便于扩展与维护。

总结

将复杂的依赖追踪逻辑拆分为独立的 system.ts 模块,并将 effect 重构为更易维护的 ReactiveEffect 类。

现在,响应式的核心由三部分组成:RefImpl(负责数据内容)、ReactiveEffect(处理副作用)、system.ts(建立依赖关系)。

最后更新时间: