Files
UI/packages/core/frame/src/signal.ts
T

65 lines
2.2 KiB
TypeScript

// 当前正在收集依赖的 effect;null 表示非追踪态
let active: (() => void) | null = null;
// 当前 effect 已订阅到的「信号订阅集」集合:重跑前据此清旧订阅,注销时据此摘干净
let activeDeps: Set<Set<() => void>> | null = null;
/*** 响应式信号:无参调用读值并收集依赖,传参写值并触发订阅 ***/
export interface Signal<T> {
(): T;
(value: T): void;
}
/*** 创建信号:框架状态的最小单元 ***/
export function signal<T>(initial: T): Signal<T> {
let value = initial;
const subs = new Set<() => void>();
const read = (...args: unknown[]) => {
if (args.length === 0) {
if (active) {
subs.add(active); // 依赖收集
activeDeps?.add(subs); // 同时记下「我订阅了哪个集」,注销时才能摘干净
}
return value;
}
const v = args[0] as T;
if (Object.is(v, value)) return; // 值未变不触发
value = v;
for (const fn of [...subs]) fn(); // 拷贝后触发,防遍历中修改
};
return read as Signal<T>;
}
/*** 副作用:立即执行一次并追踪内部读取的信号,依赖变化自动重跑;返回注销函数 ***/
export function effect(fn: () => void): () => void {
const deps = new Set<Set<() => void>>(); // 本 effect 订阅到的所有信号订阅集
let stopped = false; // 注销标志:注销后即使还在某个订阅集里也不再执行
const run = () => {
if (stopped) return; // 卫语句:注销后不再跑
for (const subs of deps) subs.delete(run); // 清上一轮订阅:条件分支换掉后的旧依赖不该再唤它
deps.clear();
const prev = active;
const prevDeps = activeDeps;
active = run;
activeDeps = deps;
try {
fn();
} finally {
active = prev;
activeDeps = prevDeps;
}
};
run();
return () => {
stopped = true;
for (const subs of deps) subs.delete(run); // 真摘订阅:否则卸载后的组件仍会被信号唤醒
deps.clear();
};
}
/*** 计算值:由信号派生的只读视图,同样参与上层依赖收集 ***/
export function computed<T>(fn: () => T): () => T {
const out = signal<T>(undefined as T);
effect(() => out(fn()));
return () => out();
}