我带过很多弟子,发现他们从入门到开悟,往往都卡在一个名叫「变更检测」的坎上。他们总觉得视图更新像一种神秘的「魔法」,时而灵验无比,时而又莫名其妙地给你一个 ExpressionChangedAfterItHasBeenCheckedError 的「诅咒」,让人摸不着头脑。
这背后并非魔法,而是 Angular 「心脏」的一次搏动。能否掌控这颗心脏的跳动节奏,是区分「玩家」与「大师」的分水岭。今天,雪狼就向你传授这门与时俱进的「心法」。
初阶心法:Zone.js 的「天罗地网」#
在 Signals 出现之前,Angular 的响应式魔法,几乎完全依赖于它的左膀右臂 —— Zone.js。
你可以把它想象成一张覆盖整个应用的、无形的「天罗地网」 。它通过「猴子补丁」(Monkey-patching)技术,将浏览器里所有可能引起变化的异步 API(setTimeout, Promise, addEventListener 等)全部「劫持」了。只要网中任何一个地方有风吹草动,它都会立刻向 Angular 发出警报:「嘿,有事发生了,快检查一下!」
这个警报,就是触发 Angular 从上到下、进行一次全局变更检测的「扳机」。这就像一个神经过敏的新手,听到一点声音就全身紧绷,做出一次完整的戒备动作。
-
优点:对开发者来说,很「自动」,很省心。
-
缺点:效率极低。不管动静大小,总是把整棵组件树都犁一遍(当然,实现上做了很多优化)。性能开销大,还容易「走火入魔」(出现
ExpressionChanged...错误)。
进阶心法:OnPush 的「静心诀」#
为了对抗 Zone.js 的「过度警觉」,我们这些老江湖摸索出了一套 OnPush 的「静心诀」。通过在组件上标注 changeDetection: ChangeDetectionStrategy.OnPush,我们等于告诉这个组件:「进入禅定,不要理会外界的纷纷扰扰。只有当你的『口粮』(@Input 引用)变了,或者你自己内部有明确的事件发生时,才需要醒来检查一次。」
OnPush 极大地提升了性能,但也要求我们严格遵守「不可变数据」的戒律,增加了修行难度。它是在 Zone.js 体系下,我们能达到的最佳「手动优化」。
大师心法:Signals 的「无心之境」#
Angular Signals 的出现,则彻底颠覆了这门心法。它不再需要 Zone.js 的「天罗地网」,而是由内而外,构建了一张精准的、点对点的「依赖图谱」。
-
心法奥义:当一个组件的模板中读取了某个
signal的值(mySignal()),它就在自己和这个signal之间,建立了一条直接的「因果线」。 -
运行机制:当
mySignal的值被更新时,它会像内力传导一样,沿着这些「因果线」,只通知那些真正依赖它的组件去更新自身。
如果说 Zone.js 是「一人有事,全村广播」,那么 Signals 就是「点对点私信,谁家有事,只通知谁家亲戚」。它达到了一种「无心而动,动必有因」的至臻境界。
新旧心法对比:
假设你要做一个简单的计数器。
用旧心法(OnPush + BehaviorSubject),你需要服务、Observable、OnPush、async 管道……一套组合拳才能打好。
而用新心法(Signal-based),代码锐减,逻辑直观:
@Component({
selector: 'app-new-counter',
template: `Count: {{ count() }}`, // 直接调用
standalone: true,
})
export class NewCounterComponent {
count = signal(0);
increment() { this.count.update(c => c + 1); }
}signal 的更新会自动、精准地通知到模板中的 {{ count() }},并且只更新这一小块视图。这就是大师心法的威力:用最少的消耗,造成最精准的效果。
演进之路:从「他控」到「内生」#
Angular 变更检测的「心法」演进,其实是一条从「依赖外力」到「修炼内功」的修行之路。
-
CheckAlways (默认):完全依赖外力。由 Zone.js 这个外部「神明」掌控一切。
-
OnPush:半内功。开发者通过遵循戒律,获得了干预变更检测的能力,但仍活在 Zone.js 的「阴影」下。
-
Signals:纯内功。响应式能力「内生」于状态自身,不再需要外部的「天罗地网」。

结语:庖丁解牛,游刃有余#
对变更检测的理解深度,直接决定了你应用的性能上限。
在《庄子》中,有一位叫庖丁的厨师,他解剖牛的技术出神入化。文惠君问他秘诀,他回答说,他靠的是顺应牛的天然肌理,以无厚入有间。他解牛十九年,刀刃却依然像新磨的一样。
今臣之刀十九年矣,所解数千牛矣,而刀刃若新发于硎。
(如今我的刀用了十九年,解剖过数千头牛,但刀刃还像刚从磨刀石上磨出来一样锋利。)
从 Zone.js 到 Signals 的进化,就是从「砍骨头」到「庖丁解牛」的进化。当你不再需要和变更检测的「骨头」作斗争,而是能顺应 Signals 的「肌理」去构建应用时,你的代码自然就达到了那种游刃有余、毫无损耗的境界。
那一刻,你才算真正掌握了 Angular 的「心法」。