多年以来,Angular 的响应式体系,一直由一位强大、神秘、却又时常引发「烦恼」的「幕后功臣」所驱动 —— 它就是 Zone.js。它就像一张「天罗地网」,自动捕获所有异步事件,并触发变更检测。这很「魔法」,但有时,也是「黑魔法」的源头(ExpressionChangedAfterItHasBeenCheckedError,性能问题等)。
现在,一个全新的「门派」崛起了。它光明正大,无需「魔法」,讲究的是「精准打击」,而非「地毯式轰炸」。它,就是 Angular Signals。
Signals 的到来,不亚于 Angular 内部的一场「心脏手术」。它预示着一个更简单、更高效、甚至最终可能「告别 Zone.js」的「新纪元」的开启。
旧时代的回响:Zone.js 的「天罗地网」#
简单回顾一下,Zone.js 的工作模式是「全局广播」:
-
它像一个「村口大喇叭」,任何异步事件(点击、
setTimeout等)一发生,它就向全村(整个应用)广播:「有情况了,大家注意!」 -
听到广播后,Angular 会启动一轮从上到下的、地毯式的变更检测,检查每一个组件,看看有谁需要更新。
-
优点:开发者体验好,你只需改变一个属性,UI 「自动」就更新了。
-
烦恼:效率低下,很多「没情况」的组件也被迫接受检查;同时,这套「魔法」也带来了
ExpressionChangedAfterItHasBeenCheckedError等难以理解的「诅咒」。
新纪元的主角:Signals 的「点对点私信」#
Signal 是一种全新的响应式基础原语。它的核心思想,彻底颠覆了 Zone.js 的模式。
如果说 Zone.js 是「村口大喇叭」,那么 Signal 就是一个「点对点的私信系统」。

它的工作流程如下:
-
signal(initialValue):创建一个signal,就像创建了一个「私密聊天群」。 -
mySignal():在模板或computed中「读取」一个signal的值。这个动作,就像是你加入了这个聊天群,自动完成了「订阅」。Signal 会默默记下:「哦,这个组件对我的值感兴趣。」 -
.set(newValue)或.update(fn):修改signal的值。这就像你在聊天群里发了一条新消息。 -
精准通知:Signal 会立即翻阅它的「订阅者列表」,然后只向那些真正关心它变化的组件发送「私信」:「我变了,你该更新了!」
这个过程里,没有全局广播,没有从上到下的遍历,只有高效、精准的「点对点」通知。
Signal 的「三板斧」:signal, computed, effect#
1. signal:你的响应式「宝盒」#
它是一个包裹着你的值的「宝盒」。你可以 .set() 一个全新的宝物进去,或者 .update() 对盒中的宝物进行加工。
const count = signal(0); // 创建一个装着 0 的宝盒
count.set(5); // 放入新宝物 5
count.update(currentValue => currentValue + 1); // 加工一下,现在是 6
console.log(count()); // 打开宝盒,看看现在是 6
2. computed:依赖你我的「智能合约」#
computed 用于创建派生状态。它像一份「智能合约」,能根据其他一个或多个 signal 的值,自动计算出自己的值。
const firstName = signal('Wang');
const lastName = signal('Zhicheng');
const fullName = computed(() => `${firstName()} ${lastName()}`);
// 当 firstName 或 lastName 变化时, fullName 会自动更新
computed 的美妙之处在于它的懒加载和记忆化特性:
-
懒:只有当有人真正需要
fullName()的值时,它才会去计算。 -
记忆:如果
firstName和lastName都没有变,即使你调用fullName()一百次,那个拼接字符串的函数也只会执行一次。结果会被缓存起来。
3. effect:连接响应式世界与外部的「传送门」#
effect 是一个「传送门」,用于处理「副作用」(Side Effects)。当它内部依赖的任何 signal 发生变化时,它就会自动重新运行。
effect(() => {
// 当 count 这个 signal 变化时,下面这行代码会自动执行
console.log(`The current count is: ${count()}`);
localStorage.setItem('count', count()); // 比如,同步到 localStorage
});effect 是你与「响应式世界」之外沟通的桥梁。比如,你需要根据某个 signal 的值,去手动操作一个非 Angular 的第三方库,或者只是简单地打印日志。
告别 Zone.js 的「烦恼」#
有了 Signals,我们终于有了告别 Zone.js 的底气。
-
再见,
OnPush和不可变数据:在 Signal-based 组件中,你不再需要OnPush策略,也不再需要为了触发变更检测而小心翼翼地创建不可变对象了。signal的精准通知机制,天然就拥有比OnPush更高的效率。 -
再见,
ExpressionChangedAfterItHasBeenCheckedError:因为没有了「从上到下」的检查周期,也就没有了「检查后变更」这个概念。Signal 的更新是同步的、可预测的,从根本上消除了这类错误的发生土壤。 -
拥抱「无 Zone」未来:Angular 团队的目标是,最终让开发者可以完全选择不使用
Zone.js来构建应用。这意味着更小的打包体积、更可控的性能、以及与其他 JS 生态更好的互操作性。每一行你用 Signal 写的代码,都是在为这个「无 Zone」的未来投票。
结语#
Signals 不是对 RxJS 的替代。RxJS 依然是处理复杂异步事件流(比如多个 switchMap 串联)的「屠龙刀」。而 Signals,则成为了处理和同步「状态」的「倚天剑」。
它代表了 Angular 响应式模型的一次深刻进化:从隐式的、粗粒度的「魔法」,走向显式的、细粒度的「科学」。
这不仅仅是一个新功能,这是一个「新纪元」的开始。
正如古人云:「芳林新叶催陈叶,流水前波让后波。」 —— 意指新生事物取代旧事物是自然规律。Signals 的出现,正是 Angular 响应式编程领域「新叶」与「后波」的强劲势头,昭示着更高效、更直观的开发未来。现在,就是学习和拥抱它的最佳时机。