我带过很多弟子,发现他们从入门到开悟,往往都卡在一个名叫「变更检测」的坎上。他们总觉得视图更新像一种神秘的「魔法」,时而灵验无比,时而又莫名其妙地给你一个 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),你需要服务、ObservableOnPushasync 管道……一套组合拳才能打好。

而用新心法(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 变更检测的「心法」演进,其实是一条从「依赖外力」到「修炼内功」的修行之路。

  1. CheckAlways (默认):完全依赖外力。由 Zone.js 这个外部「神明」掌控一切。

  2. OnPush半内功。开发者通过遵循戒律,获得了干预变更检测的能力,但仍活在 Zone.js 的「阴影」下。

  3. Signals纯内功。响应式能力「内生」于状态自身,不再需要外部的「天罗地网」。

文生图:一条从左到右的演进路径。左边是巨大的Zone.js之网笼罩一切。中间是OnPush,一个人在网下打坐,身旁有结界。右边是Signals,网已消失,许多发光的点(Signals)通过光纤(依赖)连接,形成一个高效的网络。

结语:庖丁解牛,游刃有余#

对变更检测的理解深度,直接决定了你应用的性能上限。

在《庄子》中,有一位叫庖丁的厨师,他解剖牛的技术出神入化。文惠君问他秘诀,他回答说,他靠的是顺应牛的天然肌理,以无厚入有间。他解牛十九年,刀刃却依然像新磨的一样。

今臣之刀十九年矣,所解数千牛矣,而刀刃若新发于硎。

(如今我的刀用了十九年,解剖过数千头牛,但刀刃还像刚从磨刀石上磨出来一样锋利。)

从 Zone.js 到 Signals 的进化,就是从「砍骨头」到「庖丁解牛」的进化。当你不再需要和变更检测的「骨头」作斗争,而是能顺应 Signals 的「肌理」去构建应用时,你的代码自然就达到了那种游刃有余、毫无损耗的境界。

那一刻,你才算真正掌握了 Angular 的「心法」。