本文深入解析了响应式系统核心(Reactive System Core)的工作原理,作为连接信号(Signal)、计算属性(Computed)和副作用(Effect)的"大脑"。核心通过link函数建立依赖关系,propagate函数传播变化,以及updateComputed/notifyEffect等函数调度执行更新,确保数据变化时相关计算和操作能自动高效执行。文章通过戏剧导演和交通调度系统的比喻,形象说明了这个"幕后协调者"如何管理状态变更、依赖追踪和更新传播的完整流程,并展示了核心函数在signal、computed、effect等API中的实际调用方式。
在前三章中,我们分别学习了 信号 (Signal) 如何存储基本状态,计算属性 (Computed) 如何根据状态派生新状态,以及 副作用 (Effect) 如何响应状态变化并执行操作。你可能已经想过:它们是如何协同工作的?是什么在幕后连接这一切,确保当一个信号变化时,依赖它的计算属性会更新,进而触发相关的副作用执行呢?
答案就是我们这一章的主角:响应式系统核心 (Reactive System Core)。
想象一下你正在导演一部戏剧。你有演员(信号)、有需要根据其他演员台词即兴发挥的演员(计算属性)、还有负责在特定剧情点触发灯光或音效的技术人员(副作用)。
那么,谁来确保:
这个协调者,就是 响应式系统核心 (Reactive System Core)。它不是我们直接在应用代码里频繁调用的东西,而是 master
内部的“引擎”或“大脑”。它通过一个名为 createReactiveSystem
的内部函数创建,并提供了一系列底层工具函数,供 signal
, computed
, effect
等我们熟悉的 API 在内部使用。
就像一个城市的中央交通调度系统:
没有这个核心系统,信号、计算属性和副作用就只是一盘散沙,无法形成一个自动响应变化的有机整体。
响应式系统核心主要负责以下几项关键工作,这些工作通常由它内部的一些函数来完成:
link
):谁依赖谁?当你在一个计算属性 (Computed) 或 副作用 (Effect) 的函数内部读取一个信号 (Signal) 时,系统需要记录下这种依赖关系。就好比你订阅了一份报纸,报社需要知道要把报纸寄给你。
核心系统提供的 link
函数就负责这个任务。它会在信号和它的读取者(订阅者,Subscriber)之间建立一个链接。
computed
的计算函数执行并读取 signal()
时,或者当 effect
的函数执行并读取 signal()
时。link
函数会把当前的 computed
或 effect
(称为订阅者 sub
) 添加到被读取的 signal
(称为依赖 dep
) 的订阅者列表 (subs
) 中。同时,也会把这个 signal
添加到 computed
或 effect
的依赖列表 (deps
) 中。这是一个双向记录的过程。// 简化示意:当 effect 读取 signal 时
function someEffectFunction() {
const value = mySignal(); // 读取信号
// 在 mySignal() 内部,如果检测到当前有活动的 effect (activeSub),
// 就会调用核心的 link(mySignalObject, activeEffectObject)
}
这个链接过程是依赖追踪与链接 (Tracking & Linking) 的核心部分,我们将在下一章详细探讨。
propagate
):信号灯变了!当一个信号 (Signal) 的值被写入新值时,它需要通知所有依赖它的订阅者(比如计算属性 (Computed) 或 副作用 (Effect))。
核心系统提供的 propagate
函数负责这个“广播”任务。它会遍历信号的所有订阅者,并通知它们:“嘿,你关注的数据变了!”
mySignal(newValue)
并且 newValue
与旧值不同时。propagate
会找到所有通过 link
建立关系的订阅者。对于每个订阅者,它会设置一个状态标记(比如 Dirty
表示需要重新计算,PendingComputed
或 PendingEffect
表示需要检查或执行)。这就像交通调度系统把依赖该信号的路口的信号灯变红或变黄,提示需要注意。// 简化示意:signal 写入逻辑
function signalGetterSetter(newValue) {
if (this.currentValue !== newValue) {
this.currentValue = newValue;
const subs = this.subs; // 获取订阅者列表
if (subs !== undefined) {
propagate(subs); // 调用核心函数,通知所有订阅者
// ... 可能还有后续的调度逻辑 ...
}
}
}
notifyEffect
, updateComputed
, processEffectNotifications
):指挥交通!仅仅标记订阅者“需要更新”还不够,系统还需要决定何时以及如何真正执行更新。比如,计算属性应该在被读取时才重新计算(惰性),而副作用则应该在依赖变化后尽快执行(但可能需要批量处理)。
核心系统提供了一些函数来管理这个调度过程:
updateComputed
:当一个被标记为 Dirty
的计算属性 (Computed) 被读取时,这个函数(或者调用它的 processComputedUpdate
)会被触发。它负责:startTracking
)。getter
)。link
建立新的依赖关系。endTracking
),清理旧的、不再需要的依赖。propagate
给下游依赖者。notifyEffect
:当一个副作用 (Effect) 被标记为需要执行时(例如,在 propagate
之后),这个函数(或者调用它的 processEffectNotifications
)会被触发。它负责:startTracking
)。fn
)。link
建立或确认依赖关系。endTracking
)。processEffectNotifications
:这个函数通常在一次或一批更新操作结束后被调用。它会检查所有被标记为需要执行的副作用,并调用 notifyEffect
来实际执行它们。这有助于批量处理 (Batching),避免同一个副作用在短时间内被触发多次。// 简化示意:计算属性读取逻辑
function computedGetter() {
// 检查是否标记为 Dirty 或 PendingComputed
if (this.flags & (SubscriberFlags.Dirty | SubscriberFlags.PendingComputed)) {
// 调用核心函数来处理计算和更新
processComputedUpdate(this, this.flags);
}
// ... 返回缓存的值 ...
}
// 简化示意:信号写入后的处理
function signalGetterSetter(newValue) {
// ... 更新值,调用 propagate ...
if (!batchDepth) { // 如果不在批量处理模式
processEffectNotifications(); // 立即处理需要执行的 effect
}
}
这些调度函数确保了更新以一种高效且可预测的方式进行。
让我们通过一个简单的例子,看看响应式系统核心是如何协调信号、计算属性和副作用的:
假设我们有:
firstName = signal("张")
lastName = signal("三")
fullName = computed(() => firstName() + lastName())
greetingEffect = effect(() => console.log("你好, " + fullName() + "!"))
初始状态下:
greetingEffect
首次执行,读取 fullName()
。fullName()
首次执行(因为它是惰性的),读取 firstName()
和 lastName()
。link
) 建立依赖关系:firstName
-> fullName
lastName
-> fullName
fullName
-> greetingEffect
现在,用户执行 firstName("李")
:
流程解读:
firstName("李")
触发 propagate
。fullName
被标记为 Dirty
,greetingEffect
被标记为 PendingEffect
并加入待处理队列。processEffectNotifications
开始处理队列。greetingEffect
,系统准备执行它的函数。greetingEffect
的函数读取 fullName()
。fullName
发现自己是 Dirty
,触发 processComputedUpdate
。processComputedUpdate
内部重新执行 fullName
的 getter
,再次读取 firstName
和 lastName
,并确认依赖关系 (link
)。计算出新值 "李三"。fullName
缓存 "李三" 并清除 Dirty
标记,然后将新值返回给 greetingEffect
。greetingEffect
使用新值 "李三" 完成其 console.log
操作。endTracking
清理追踪状态。整个过程由响应式系统核心在幕后精确地协调和调度。
我们不会深入 createReactiveSystem
的所有细节,但可以看看它是如何在 src/index.ts
中被使用的,以及它返回的一些核心函数是如何被 signal
等调用的。
createReactiveSystem
的使用在 src/index.ts
的开头,你会看到 createReactiveSystem
被调用,并传入了两个关键的配置函数:updateComputed
和 notifyEffect
。这就像是告诉核心引擎“当你需要更新计算属性时,就这样做”以及“当你需要通知副作用时,就这样做”。
// src/index.ts (部分)
// 导入核心类型和创建函数
import { createReactiveSystem, Dependency, Subscriber, SubscriberFlags } from './system.js';
// ... 其他接口定义 ...
// 调用 createReactiveSystem 来创建核心实例
// 并解构出核心提供的函数 (link, propagate 等)
const {
link,
propagate,
updateDirtyFlag,
startTracking,
endTracking,
processEffectNotifications,
processComputedUpdate,
processPendingInnerEffects,
} = createReactiveSystem({
// 配置项:如何更新 Computed
updateComputed(computed: Computed): boolean {
// ... (设置 activeSub, startTracking)
try {
const oldValue = computed.currentValue;
// 执行用户提供的 getter
const newValue = computed.getter(oldValue);
if (oldValue !== newValue) {
computed.currentValue = newValue; // 更新缓存
return true; // 值改变了
}
return false; // 值没变
} finally {
// ... (恢复 activeSub, endTracking)
}
},
// 配置项:如何通知 Effect 或 EffectScope
notifyEffect(e: Effect | EffectScope) {
if ('isScope' in e) { // 区分 Effect 和 EffectScope
return notifyEffectScope(e); // (我们将在后面章节学习 EffectScope)
} else {
return notifyEffect(e); // 调用内部的 effect 通知逻辑
}
},
});
解释:
createReactiveSystem
返回一个包含多个底层函数的对象,这些函数是响应式机制的核心。updateComputed
和 notifyEffect
函数定义了当核心系统决定要更新计算属性或执行副作用时,具体应该执行什么操作(主要是调用用户提供的 getter
或 fn
,并管理依赖追踪)。这些由 createReactiveSystem
返回的函数,被 signal
, computed
, effect
等函数在内部广泛使用。
信号写入 (signalGetterSetter
) 调用 propagate
:
// src/index.ts (signalGetterSetter 写入部分简化)
function signalGetterSetter<T>(this: Signal<T>, ...value: [T]): T | void {
if (value.length) { // 检查是否是写入操作
if (this.currentValue !== (this.currentValue = value[0])) { // 检查值是否改变
const subs = this.subs; // 获取订阅者列表
if (subs !== undefined) {
propagate(subs); // !! 调用核心函数传播变化 !!
if (!batchDepth) { // 如果不在批量模式
processEffectNotifications(); // !! 调用核心函数处理 effect !!
}
}
}
} else {
// ... 读取逻辑 ...
}
}
计算属性读取 (computedGetter
) 调用 processComputedUpdate
和 link
:
// src/index.ts (computedGetter 简化)
function computedGetter<T>(this: Computed<T>): T {
const flags = this.flags;
// 检查是否需要更新 (Dirty 或 PendingComputed)
if (flags & (SubscriberFlags.Dirty | SubscriberFlags.PendingComputed)) {
// !! 调用核心函数处理计算和更新 !!
processComputedUpdate(this, flags);
}
if (activeSub !== undefined) { // 如果当前有更高层级的订阅者在读取这个 computed
link(this, activeSub); // !! 调用核心函数建立链接 !!
}
// ... (处理 activeScope 的链接) ...
return this.currentValue!; // 返回缓存的值
}
副作用创建 (effect
) 调用 startTracking
, endTracking
, link
:
// src/index.ts (effect 函数简化)
export function effect<T>(fn: () => T): () => void {
const e: Effect = { /* ... 创建 effect 对象 ... */ };
// ... (处理 activeSub 或 activeScope 的链接) ...
const prevSub = activeSub;
activeSub = e; // 将当前 effect 设为活动订阅者
startTracking(e); // !! 调用核心函数开始追踪 !!
try {
e.fn(); // 执行用户函数 (内部读取信号时会调用 link)
} finally {
endTracking(e); // !! 调用核心函数结束追踪 !!
activeSub = prevSub; // 恢复之前的活动订阅者
}
return effectStop.bind(e); // 返回停止函数
}
这些例子展示了我们之前学习的 signal
, computed
, effect
是如何依赖响应式系统核心提供的底层能力来工作的。
在本章中,我们揭开了 master
响应式系统幕后的“大脑”——响应式系统核心 (Reactive System Core)。
createReactiveSystem
创建的引擎,负责协调整个响应式流程。signal
, computed
, effect
都依赖它提供的底层能力。link
: 建立依赖 (Dependency)和订阅者 (Subscriber)之间的连接。propagate
: 在依赖变化时,传播通知,标记需要更新的订阅者。updateComputed
, notifyEffect
, processEffectNotifications
: 调度和执行计算属性的重新计算以及副作用的重新运行,并处理优化(如批量处理)。理解响应式核心的存在和职责,有助于我们更深入地理解为什么 master
的响应式系统能够自动、高效地工作。
我们已经了解了响应式核心的整体作用,以及它提供的几个关键功能。其中,依赖追踪和链接 (link
) 是实现自动化的基础——系统必须先知道“谁依赖谁”,才能在变化发生时进行正确的通知。
在下一章,我们将聚焦于这个过程,深入探讨 link
, startTracking
, endTracking
等函数是如何工作的,以及依赖关系是如何被精确地建立和维护的。