65 lines
2.2 KiB
TypeScript
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();
|
|
}
|