2025.09.04
职责分离:解耦依赖收集与更新派发
分离关注点:重构响应式系统的依赖收集与触发
🚀 重构的驱动力:单一职责原则
虽然我们目前的响应式系统已经具备了基本功能,但现有的 RefImpl
类承担了过多职责(管理数据、收集依赖、触发更新),这明显违反了单一职责原则 (Single Responsibility Principle)。
重构目标: 将依赖收集和更新触发逻辑拆分为独立模块,以实现:
- ✅ 清晰的职责分离: 各模块只做一件事。
- ✅ 高可维护性: 易于理解和修改。
- ✅ 高扩展性: 为
computed和reactive等复杂功能做好铺垫。
原始实现:职责混杂的 RefImpl
在重构之前,RefImpl 像一个“全能选手”,直接包含了复杂的链表操作和更新逻辑,使得核心数据管理被细节代码淹没。
class RefImpl {
// ... 链表头尾指针 subs / subsTail
get value() {
// ❌ 职责混杂点 1:在这里判断 activeSub 并调用 trackRef
if (activeSub) {
trackRef(this)
}
return this._value
}
set value(newValue) {
this._value = newValue
// ❌ 职责混杂点 2:在这里调用 triggerRef
triggerRef(this)
}
}
// trackRef 和 triggerRef 中包含了大量的链表操作细节...
重构:抽离核心逻辑到 system.ts
我们将依赖关系的建立 (track) 和更新的传播 (trigger) 视为一个独立的机制模块,将其抽离到 system.ts 中。
system.ts: 依赖机制核心: 负责链表的管理和更新传播。// system.ts // ... 引入 Link 接口 /** * 🔗 link:O(1) 建立依赖链表关系 (依赖收集) * @param dep 依赖项 (RefImpl 实例) * @param sub 订阅者 (activeSub / effect 函数) */ export function link(dep, sub) { // ... 核心的双向链表尾部追加逻辑 (O(1)) } /** * 📢 propagate:遍历链表并传播更新 (触发更新) * @param subs 订阅者链表的头节点 */ export function propagate(subs) { // ... 核心的链表遍历与 effect 收集/执行逻辑 }effect.ts:副作用函数的执行与上下文 我们将副作用函数封装为ReactiveEffect类,专注于管理其执行上下文。
| 模块职责 | 说明 |
|---|---|
activeSub | 全局变量, 用来追踪当前正在执行的 ReactiveEffect 实例 |
ReactiveEffect | 封窗 fn, 提供 run 方法来设置/清除 activeSub 上下文 |
// effect.ts
export let activeSub: ReactiveEffect | undefined
export class ReactiveEffect {
constructor(public fn: Function) { /* ... */ }
run(): any {
activeSub = this // 📌 标记开始
try {
return this.fn() // 运行 fn,触发 getter/track
}
finally {
activeSub = undefined // 🧹 标记结束
}
}
}
3.ref.ts: 职责回归纯粹
重构后的 ref.ts 只负责将外部的 trackRef 和 triggerRef 调用转发给 system.ts 中的核心逻辑。
// ref.ts - 重构后
import { activeSub } from './effect'
import { link, propagate } from './system' // 引入核心机制
// ... RefImpl 类定义
export function trackRef(dep: RefImpl) {
// 仅负责检查 activeSub,并将 dep/sub 转发给 system.link
if (activeSub) {
link(dep, activeSub)
}
}
export function triggerRef(dep: RefImpl) {
// 仅负责检查是否存在订阅者,并转发给 system.propagate
if (dep.subs) {
propagate(dep.subs)
}
}
class RefImpl {
// ...
get value() {
// 仅调用模块暴露的 API
if (activeSub) {
trackRef(this)
}
return this._value
}
set value(newValue: T) {
this._value = newValue
// 仅调用模块暴露的 API
triggerRef(this)
}
}
为什么将 effect 设计为类?
将副作用函数实现为 ReactiveEffect 类而非简单函数,是实现高级响应式功能的基石:
| 优势 | 描述 | 对应未来功能 |
|---|---|---|
| 状态封装 | 类结构天然适合封装 effect 内部状态(如: 依赖了哪些数据、是否已经停止) | 依赖清理 (Cleanup),精确停止 (stop)。 |
| 行为扩展 | 可以轻松为 effect 实例添加方法和属性 | 自定义调度 (scheduler)、懒执行 (lazy)。 |
| 类型安全 | 确保所有依赖项存储的都是具有完整生命周期管理的 ReactiveEffect 实例。 |
重构成果
通过这次重构,我们实现了核心关注点的清晰分离,让系统变得健壮和模块化:
| 模块 | 职责 | 依赖/关系 |
|---|---|---|
system.ts | 依赖关系管理 | 专注于高效的链表操作 (link, propagate)。 |
effect.ts | 副作用函数上下文 | 负责 ReactiveEffect 的生命周期和 activeSub 标记。 |
ref.ts | 响应式数据封装 | 负责 RefImpl 数据管理,通过 trackRef/triggerRef 桥接。 |
这种模块化的设计不仅使代码更加清晰易读,还为未来的功能扩展和性能优化奠定了坚实的基础。