Appearance
问题背景
代码虽然能跑,但 RefImpl
的 get value
和 set 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
接着通过重构,将 trackRef
和 triggerRef
从 RefImpl
中抽离,放到独立模块中。
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 设计为一个类
- 状态封装:
effect
本身是有状态的(它依赖了谁、是否正在执行)。类更适合封装这类状态与行为。 - 功能扩展:
effect
未来可能需要更多能力(如停止、调度等),用类便于扩展与维护。
总结
将复杂的依赖追踪逻辑拆分为独立的 system.ts
模块,并将 effect
重构为更易维护的 ReactiveEffect
类。
现在,响应式的核心由三部分组成:RefImpl
(负责数据内容)、ReactiveEffect
(处理副作用)、system.ts
(建立依赖关系)。