基础使用
signal
一个包含getter
和setter
的对象,在执行getter
和setter
的时候会触发额外的逻辑computed
和 vue 中的概念一样effect
副作用函数,当signal setter
执行后,effect
函数会自动再执行一次effectScope
副作用函数作用域,可以使该作用域内的effect
失效Batch
批处理,对要执行的effect
函数进行去重处理
scss
import { signal, computed, effect } from 'alien-signals';
const count = signal(1);
const doubleCount = computed(() => count() * 2);
effect(() => {
console.log(`Count is: ${count()}`);
}); // Console: Count is: 1
console.log(doubleCount()); // 2
count(2); // Console: Count is: 2
console.log(doubleCount()); // 4
scss
import { signal, effect, effectScope } from 'alien-signals';
const count = signal(1);
const stopScope = effectScope(() => {
effect(() => {
console.log(`Count in scope: ${count()}`);
}); // Console: Count in scope: 1
});
count(2); // Console: Count in scope: 2
stopScope();
count(3); // No console output
底层原理
推拉混合模型
Push Model
- 数据变化时立即通知依赖项(高效但可能过度通知)Pull Model
- 需要时才计算最新值(精确但可能延迟通知)Push-Pull Hybrid Model
- 用 Push 方式通知可能变了,用 Pull 方式按需计算 "具体值"

Push 阶段的工作:
- 标记为脏:
Signal
将自己标记为Dirty
- 传播通知:通过 propagate() 递归通知所有依赖的 Computed 和 Effect
- 标记为待处理:将依赖项标记为 Pending
- 加入队列:将 Effect 加入执行队列 queuedEffects
Pull 阶段的工作:
- 惰性检查:只有当 Computed 被读取时,才检查自己是否为脏
- 按需计算:只有确实为脏才执行计算函数
- 建立依赖:计算过程中读取其他 Signal,建立新的依赖关系
- 缓存结果:计算完成后缓存结果,清除脏标记
双向链表结构动态更新订阅依赖关系
每个依赖关系通过 Link 对象表示,它同时属于两个双向链表
- 依赖链表: 从订阅者角度,指向它依赖的所有节点
- 订阅链表:从被依赖者角度,指向所有订阅它的节点
perl
// 定义响应式节点的接口
export interface ReactiveNode {
deps?: Link; // 该节点所依赖的其他节点的链表头(作为依赖者,指向其依赖项)
depsTail?: Link; // 依赖链表的尾部,用于高效追加
subs?: Link; // 订阅该节点的其他节点的链表头(作为被依赖者,指向其订阅者)
subsTail?: Link; // 订阅链表的尾部,用于高效追加
flags: ReactiveFlags; // 节点的状态标志位
}
interface Link {
version: number;
dep: ReactiveNode; // 被依赖的节点(如 Signal)
sub: ReactiveNode; // 订阅的节点(如 Effect/Computed)
prevDep: Link | undefined; // 前一个同一 sub 的依赖
nextDep: Link | undefined; // 后一个同一 sub 的依赖
prevSub: Link | undefined; // 前一个同一 dep 的订阅
nextSub: Link | undefined; // 后一个同一 dep 的订阅
}
-
通过版本号来优化连接管理
- 自动清理过期依赖
- 支持条件依赖
- 内存安全
perl
let currentVersion = 0;
function startTracking(sub: ReactiveNode): void {
++currentVersion; // 每次追踪开始时版本号递增
sub.depsTail = undefined;
// ...
}
function endTracking(sub: ReactiveNode): void {
// 移除所有版本号不匹配的旧链接
let toRemove = sub.depsTail !== undefined ? sub.depsTail.nextDep : sub.deps;
while (toRemove !== undefined) {
toRemove = unlink(toRemove, sub);
}
}
-
依赖关系链表的动态更新过程
示例1:一个 effect 依赖了两个 signal
yaml
const signalA = signal('A');
const signalB = signal('B');
effect(() => {
console.log(signalA());
console.log(signalB());
);
// 依赖关系
SignalA: { subs: Link1, subsTail: Link1 }
SignalB: { subs: Link2, subsTail: Link2 }
Effect1: { deps: Link1 → Link2, depsTail: Link2 }
Link1: {
dep: SignalA,
sub: Effect1,
prevDep: undefined,
nextDep: undefined,
prevSub: undefined,
nextSub: undefined
}
Link2: {
dep: SignalB,
sub: Effect1,
prevDep: Link1,
nextDep: undefined,
prevSub: undefined,
nextSub: undefined
}
示例2:两个 effect 依赖了同一个 signal
yaml
const signalA = signal('A');
const signalB = signal('B');
effect(() => {
console.log(signalA());
console.log(signalB());
);
effect(() => {
console.log(signalA());
})
// 依赖关系
SignalA: { subs: Link1 -> Link3, subsTail: Link3 }
SignalB: { subs: Link2, subsTail: Link2 }
Effect1: { deps: Link1 → Link2, depsTail: Link2 }
Effect2: { deps: Link3, depsTail: Link3 }
Link1: {
dep: SignalA,
sub: Effect1,
prevDep: undefined,
nextDep: undefined,
prevSub: undefined,
nextSub: Link3
}
Link2: {
dep: SignalB,
sub: Effect1,
prevDep: Link1,
nextDep: undefined,
prevSub: undefined,
nextSub: undefined
}
Link3: {
dep: SignalA,
sub: Effect2,
prevSub: Link1,
nextSub: undefined
}
vue 响应式系统更新
Vue 基于 Proxy/Reflect 的响应式系统是纯粹的推送模型
javascript
const reactive = (target) => {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key); // 收集当前正在运行的 effect
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
trigger(target, key); // 立即触发所有依赖的 effects
return result;
}
});
};
- 过度通知问题
arduino
const config = reactive({
theme: 'dark',
language: 'en',
// ... 很多其他配置
});
effect(() => {
// 只使用 theme
applyTheme(config.theme);
});
// 修改 language 也会触发 effect!
config.language = 'zh'; // 不必要的重新执行
- 条件依赖的浪费
scss
const state = reactive({
showMessage: false,
message: 'Hello'
});
effect(() => {
if (state.showMessage) {
// 只有当 showMessage 为 true 时才需要
showPopup(state.message);
}
});
// 即使 showMessage 为 false,修改 message 也会触发 effect
state.message = 'Hi'; // 浪费的计算
- 深层嵌套的性能问题
scss
const largeData = reactive({
items: Array(1000).fill().map((_, i) => ({ id: i, value: i }))
});
effect(() => {
// 只使用第一个元素
console.log(largeData.items[0].value);
});
// 修改最后一个元素也会触发整个 effect
largeData.items[999].value = 1000; // 过度触发
在 vue3.6 alpha 中响应式系统底层实现已经从原来的 proxy / reflect 迁移到 alien-signal,api 使用上和原来保持一致
Vue3.6 响应式系统: github.com/vuejs/core/...
Alien-Signal: github.com/stackblitz/...