前言:Vue 3.6 的性能飞跃,底层到底发生了什么
2025 年的 Vue.js Nation 大会上,尤雨溪宣布了 Vue 3.6 的重磅更新。在诸多新特性中,有一个看似不起眼却意义深远的改动:响应式系统核心从原有的算法切换到了 alien-signals。
社区很快注意到了这一变化。根据 benchmark 测试数据,Vue 3.6 的响应式性能比 Vue 3.4 快了约 4 倍,比 Vue 3.5 快了约 1.8 倍。更夸张的是,在读取大量 computed 值的场景下,alien-signals 的吞吐量比 Vue 3.5 高出 30 倍以上。
这些数字不是来自魔法,而是来自一个仅有 1KB(压缩后)的 TypeScript 库。
本文将深入解析 alien-signals 的设计哲学、核心算法,以及它如何影响整个前端响应式生态。
一、起源:从 Vue 内部优化到独立研究项目
alien-signals 的故事始于 Vue 核心贡献者 Johnson Chu 对 Vue 3.4 响应式系统的深度优化。他花了大量时间研究如何提升 Vue 响应式的性能,但 Vue 3.5 转向了类似 Preact 的纯 Pull-based 算法,改变了技术方向。
为了继续探索 Push-Pull 混合算法的可能性,Johnson 决定将这个研究项目独立出来------这就是 alien-signals 的诞生。
据《Alien Signals: The Tiny Reactivity Core That Powers Vue》(reactlibs.dev/articles/al... Vue 3.6 的核心代码库(PR: github.com/vuejs/core/...
这不是一个实验性项目,而是一个在生产环境中被验证过的算法。
二、设计哲学:性能优先的约束
alien-signals 在实现上施加了几个刻意为之的约束,这些约束正是它性能出色的关键:
- 核心遍历不使用 Array、Set、Map:这些数据结构会带来额外的内存分配和 GC 压力
- 禁止函数递归:避免调用栈开销和栈溢出风险
- 使用位运算状态标志:用紧凑的位标志代替单独的布尔字段
"我们发现在这些约束条件下,保持算法的简洁性比复杂的调度策略能带来更显著的性能提升。" ------ alien-signals README(github.com/stackblitz/...
三、核心 API:signal、computed、effect
alien-signals 的 API 设计极简,只需三个函数就能完成日常响应式编程。
3.1 signal:响应式容器
typescript
import { signal } from 'alien-signals';
// 创建带初始值的 signal
const count = signal(1);
// 读取:不传参数
console.log(count()); // 1
// 写入:传入新值
count(2);
console.log(count()); // 2
signal 返回一个函数,调用时不传参数用于读取,传入参数用于写入。这种设计在 SolidJS 和 Preact Signals 中也很常见,被称为"getter/setter 二合一"模式。
3.2 computed:惰性求值的派生值
typescript
import { signal, computed } from 'alien-signals';
const count = signal(10);
const doubled = computed(() => count() * 2);
const message = computed(() => `Count is ${count()}`);
console.log(doubled()); // 20
console.log(message()); // Count is 10
count(25);
console.log(doubled()); // 50(惰性:只有读取时才重新计算)
console.log(message()); // Count is 25
computed 的关键特性是惰性:当 count 变化时,doubled 不会立即重新计算,只有当你读取它时,才会检查是否需要重新求值。这是 Push-Pull 混合算法的精髓。
3.3 effect:响应式副作用
typescript
import { signal, effect } from 'alien-signals';
const count = signal(1);
effect(() => {
console.log(`Count changed to: ${count()}`);
});
// Console: Count changed to: 1
count(2);
// Console: Count changed to: 2
count(3);
// Console: Count changed to: 3
effect 会立即执行一次回调,同时自动追踪回调中读取的所有 signal。之后任何被追踪的 signal 发生变化,effect 都会自动重新运行。
3.4 effect 的清理函数
从 3.2.0 版本开始,effect 支持返回清理函数:
typescript
import { signal, effect } from 'alien-signals';
const userId = signal('user-1');
const wsStatus = signal('connected');
// 模拟 WebSocket 连接
effect(() => {
const id = userId();
const socket = connect(`/ws/${id}`);
// 清理函数:下次运行前或 effect 销毁时调用
return () => {
socket.close();
};
});
userId('user-2'); // 自动关闭旧连接,创建新连接
3.5 effectScope:批量管理 effect 生命周期
typescript
import { signal, effect, effectScope } from 'alien-signals';
const count = signal(1);
// 创建作用域
const stopScope = effectScope(() => {
effect(() => {
console.log(`In scope: ${count()}`); // 立即执行
});
});
count(2); // Console: In scope: 2
// 销毁整个作用域内的所有 effect
stopScope();
count(3); // 没有任何输出------scope 已销毁
effectScope 的典型应用场景是组件生命周期:组件挂载时创建 scope,卸载时调用 stopScope,所有 effect 都会被自动清理,不会产生内存泄漏。
四、Push-Pull 算法:两种范式的最佳融合
理解 Push-Pull 算法是理解 alien-signals 性能优势的关键。
4.1 纯 Push 模式
当 signal 变化时,立即触发所有下游 computed 和 effect 的重新计算。
优点 :值永远是最新的
缺点:即使没人读取,也会执行大量计算
4.2 纯 Pull 模式
当 signal 变化时,什么都不做。直到有代码读取 computed 时,才进行重新计算。
优点 :只计算需要用到的值
缺点:每次读取都要检查依赖是否变化,有一定开销
4.3 Push-Pull 混合:alien-signals 的选择
signal 变化 → 推送 dirty 标志 → 读取 computed → 检查 dirty → 必要时重新计算
Push 阶段:signal 变化时,只在依赖图中传播"需要更新"的标志(dirty flag),不执行任何计算。
Pull 阶段:computed 被读取时,检查自身的 dirty 状态。如果为 dirty,才执行重新计算。
这种设计带来了两个世界的最佳特性:
- Push 侧:高效的依赖追踪,变化能立即被感知
- Pull 侧:惰性求值,不会浪费计算未使用的值
据《Reactivity Optimization》(book.chibivue.land/30-basic-re...
五、双向链表:内存优化的关键
alien-signals 在依赖管理上选择了双向链表而非 Set。
传统 Vue 实现(使用 Set)
typescript
class Dep {
subscribers = new Set<ReactiveEffect>();
track() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
trigger() {
this.subscribers.forEach(effect => effect.run());
}
}
Set 很好用,但每个依赖关系都需要一个 Set 对象,有额外的内存开销和 GC 压力。
alien-signals 实现(使用双向链表)
typescript
interface Link {
dep: Dep;
sub: Subscriber;
prevDep: Link | undefined; // 同一 subscriber 的前一个 dep
nextDep: Link | undefined; // 同一 subscriber 的后一个 dep
prevSub: Link | undefined; // 同一 dep 的前一个 subscriber
nextSub: Link | undefined; // 同一 dep 的后一个 subscriber
}
每个 Link 节点只知道自己的前后邻居,没有额外的容器对象。这带来了:
- 更低的内存占用:避免 Set 的额外开销
- O(1) 的添加/删除:直接操作链表指针
- 更低的 GC 压力:没有中间容器对象需要回收
六、全局版本号:高效的脏值检测
alien-signals 使用全局递增的版本号来高效检测依赖是否变化:
typescript
let globalVersion = 0;
function triggerSignal(signal) {
globalVersion++;
signal.version = globalVersion;
// 传播 dirty 标志
}
function getComputedValue(computed) {
if (computed.globalVersion !== globalVersion) {
// 某上游依赖可能已变化
if (checkDirty(computed)) {
computed.value = computed.getter();
}
computed.globalVersion = globalVersion;
}
return computed.value;
}
当 signal 更新时递增全局版本号,下游 computed 读取时只需比较版本号,无需遍历整个依赖图。
七、脱离 Vue 的独立用法
alien-signals 最大的魅力在于它的框架无关性。它不依赖 Vue,甚至不依赖任何其他库,是一个纯粹的响应式引擎。
7.1 在 React 中使用
React 没有原生的细粒度响应式------它通过组件重渲染来更新 UI。alien-signals 不能改变这一点,但可以作为 React 外部状态管理引擎使用。
社区已有多个绑定库:
- react-alien-signals (github.com/Rajaniraiyn... 绑定
- reactjs-signal:通过 signal 模式共享 store 状态
基本用法是通过 React 18 的 useSyncExternalStore 来桥接:
typescript
import { signal, effect } from 'alien-signals';
import { useSyncExternalStore } from 'react';
// 创建 store
const count = signal(0);
// React 组件中使用
function Counter() {
// useSyncExternalStore 订阅 alien-signals 的 effect
const value = useSyncExternalStore(
(onStoreChange) => {
const dispose = effect(() => {
count(); // 追踪
onStoreChange();
});
return dispose;
},
() => count() // 服务端快照
);
return <div>Count: {value}</div>;
}
需要承认的是:alien-signals 不会让 React 的更新变得像 Solid 那样细粒度。React 的更新哲学与 signals 是相反的。但在构建跨框架的状态管理方案时,alien-signals 提供了绝佳的引擎选择。
7.2 在 XState 中使用
令人惊讶的是,状态机库 XState 也采用了 alien-signals 的核心算法来实现其 atom 架构(PR: github.com/statelyai/x...
这说明 alien-signals 的响应式算法具有足够的通用性,能够支撑起复杂的状态管理场景。
7.3 createReactiveSystem:自定义响应式系统
alien-signals 提供了一个底层工厂函数 createReactiveSystem,允许你构建自己的响应式 API:
typescript
import { createReactiveSystem } from 'alien-signals';
const { link, unlink, propagate, checkDirty } = createReactiveSystem({
update(node) {
// 重新计算派生节点,返回值是否变化
return runComputation(node);
},
notify(effectNode) {
// 调度 effect 执行
scheduleEffect(effectNode);
},
unwatched(sub) {
// 依赖失去最后一个订阅者时的清理
}
});
Vue 的响应式系统和 XState 的 atom 架构正是建立在这个底层 API 之上的。
7.4 多语言移植
alien-signals 的算法已经被移植到多种编程语言:
- Dart: medz/alien-signals-dart, void-signals/void_signals
- Lua: YanqingXu/alien-signals-in-lua, xuhuanzy/alien-signals-lua
- Luau: Nicell/alien-signals-luau
- Java: CTRL-Neo-Studios/java-alien-signals
- C#: CTRL-Neo-Studios/csharp-alien-signals
- Go: delaneyj/alien-signals-go
- Rust: wuzekang/samara-signals, ohkami-rs/alien-signals-rs
这种广泛的移植证明了算法的简洁性和语言无关性。
八、与 ECMAScript Signals 提案的关系
TC39 目前有一个处于 Stage 1 阶段的 Signals 提案(github.com/tc39/propos... JavaScript 添加原生的响应式原语。
据《JavaScript Signals Are Coming》(reptile.haus/journal/tc3... 等。
这是 JavaScript 历史上参与度最广泛的框架作者协作。
需要注意的是,TC39 Signals 提案目前专注于底层的精确语义,而不是面向开发者的 API。框架作者可以在这个共同的语义基础上构建各自的响应式 API,实现互操作性。
alien-signals 作为一种高效的响应式实现,其设计思路与 TC39 提案的方向高度一致。当 JavaScript 原生支持 Signals 后,类似 alien-signals 的库可能会成为底层实现与框架 API 之间的桥梁。
九、需要注意的坑:原地突变
alien-signals 通过值身份来追踪变化,不使用 Proxy 监听对象属性。这意味着原地修改对象或数组不会触发响应:
typescript
import { signal, computed, trigger } from 'alien-signals';
const items = signal<number[]>([]);
const length = computed(() => items().length);
console.log(length()); // 0
items().push(1);
console.log(length()); // 仍然是 0!突变被绕过了
trigger(items); // 手动触发
console.log(length()); // 1
解决方式 :要么替换整个值(items([...items(), 1])),要么使用 trigger 手动通知。
如果不想处理这个问题,社区的 alien-deepsignals(github.com/CCherry07/a... Proxy 包装,可以在 alien-signals 核心之上实现深度响应式。
十、总结:为什么值得关注的 1KB 库
alien-signals 是一个罕见的"小而美"项目:
1. 体积极小 :1-2KB(压缩后),零依赖,API 可树摇
2. 性能极致 :Vue 3.4 的 4 倍,Vue 3.5 的 30 倍(特定场景)
3. 正确性验证 :3.2.0 版本有 180 个 conformance 测试用例,包括经典的菱形依赖问题
4. 实战验证 :Vue 3.6、XState、Vue Language Tools(Volar)都在使用
5. 框架无关:可以独立使用,也可以集成到任何框架中
对于前端开发者来说,无论你使用什么框架,学习 alien-signals 都能帮助你理解现代响应式系统的核心原理。它的小体积意味着你可以花一个下午就读完源码,理解整个算法的来龙去脉。
对于框架作者来说,createReactiveSystem 提供了构建自定义响应式抽象的底层能力,Vue 和 XState 已经示范了这条路是可行的。
最后,如果你关心 JavaScript 的未来走向,TC39 Signals 提案(github.com/tc39/propos... 让我们得以提前体验一种可能的响应式语义------当那一天到来时,你会发现自己已经在"外星信号"上运行了很久。
参考来源:
- alien-signals 官方仓库:github.com/stackblitz/...
- Vue 3.6 PR (alien-signals 移植):github.com/vuejs/core/...
- 《Alien Signals: The Tiny Reactivity Core That Powers Vue》:reactlibs.dev/articles/al...
- 《Reactivity Optimization - chibivue》:book.chibivue.land/30-basic-re...
- 《JavaScript Signals Are Coming - Reptile Haus》:reptile.haus/journal/tc3...
- TC39 Signals 提案:github.com/tc39/propos...
本文由AI辅助生成