在 Angular 性能优化的武林中,流传着百般武艺:惰性加载、@for 中的 track、纯管道……但若论哪一本秘籍的威力最大、最能助你打通「任督二脉」,问鼎性能之巅,那无疑是变更检测策略中的 OnPush。
它,就是 Angular 性能优化领域的「葵花宝典」。
这本宝典,威力无穷,能让你的应用「快如闪电」。但它也并非人人可练,若内功(对数据流的理解)不纯,心法(使用姿势)不当,极易「走火入魔」,陷入更深的困惑。今天,雪狼就为你完整解读这本宝典的奥义。
默认之「咒」:草木皆兵的 CheckAlways#
在修炼宝典之前,你需知晓你所要对抗的「心魔」是什么。Angular 的默认变更检测策略 CheckAlways,就是一个「草木皆兵、宁枉勿纵」的偏执狂。
它像一个极度缺乏安全感的将军,帐篷外任何一点风吹草动(任何异步事件),都会让他把全军将士(所有组件)都叫起来,从头到脚盘问一遍。这种策略保证了「万无一失」,但也造成了巨大的、不必要的内耗。当你的「军队」规模庞大时,这种无休止的盘问,就是卡顿和掉帧的根源。
宝典心法:OnPush 的「闭关」誓言#
要修炼 OnPush,你首先要在组件的「丹田」处(@Component 装饰器),立下一个「闭关锁国」的誓言:
@Component({
//...
changeDetection: ChangeDetectionStrategy.OnPush,
})这个誓言,是在告诉 Angular:「从此刻起,我(本组件)将与外界的凡俗杂事断绝。除非符合以下三大『天道』之一,否则,无论外界如何喧嚣(Zone.js 捕获了多少异步事件),都休想让我出关(进行变更检测)。」
唤醒 OnPush 组件的三大「真言」#
一旦组件进入 OnPush 的「闭关」状态,只有三句「真言」能将它唤醒:
-
「父之赠」 ——
@Input引用变更这是最重要、最核心的一句真言。只有当父组件传递给它的
@Input属性,其引用发生了变化时,它才会醒来。-
错误姿势:
this.user.name = '雪狼';(只改了属性,user对象的地址没变,组件睡得很香,毫无反应) -
正确姿势:
this.user = { ...this.user, name: '雪狼' };(创建了一个全新的user对象,地址变了,组件被成功唤醒)
这句真言,强迫你走上「不可变数据」的光明大道。
-
-
「己之呼」 —— 组件内部事件触发
当组件自己的模板中,或其子孙组件的模板中,有事件被触发时(如
(click)),Angular 明白,这次交互很可能会改变组件自身的状态,所以它会主动唤醒该组件。 -
「流之召」 ——
async管道的新值当你在模板中使用了
async管道,并且这个管道所订阅的Observable发出了一个新值时,async管道这位「贴身信使」,会精准地将该组件标记为需要检查。这是async管道的另一大美德。
禁术:「独孤九剑」之 ChangeDetectorRef#
即便是一代宗师,有时也需要一些「破例」的招式来应对奇特的场景。ChangeDetectorRef (简称 cdr) 就是 OnPush 体系下的「独孤九剑」,它让你能手动干预变更检测的流程。但切记,剑气锋利,慎用伤身。
-
cdr.markForCheck()—— 「无招胜有招」-
剑意:这是「独孤九剑」的总决式,也是你最应该掌握的一招。它告诉 Angular:「嘿,我这里发生了一些内部变化(比如在
subscribe回调里改了数据),不符合三大真言,但我保证我的状态已经变了。请在下一次正常的变更检测循环中,把我以及我的所有祖先都『标记』上,务必检查一遍。」 -
应用场景:在
OnPush组件中,如果你必须手动订阅Observable,并在回调中改变了组件的属性,那么在改变属性之后,调用this.cdr.markForCheck()就是最标准、最安全的「破防」方式。
-
-
cdr.detectChanges()—— 「破气式」-
剑意:这是极其霸道的一招。它会立即、同步地对当前组件及其所有子组件进行一次变更检测。它像是一记强行催谷内力的「猛击」,直接打破了原有的检测节奏。
-
应用场景:极少使用! 在应用开发中,你几乎永远都不需要它。滥用
detectChanges()是导致ExpressionChangedAfterItHasBeenCheckedError的常见原因之一。它通常只在一些需要与外部非 Angular 库进行深度、同步交互的极端场景下才可能用到。
-
对于绝大多数开发者来说,请牢记:多用 markForCheck(),忘掉 detectChanges()。
结语:从「习武」到「修道」#
将 OnPush 设置为你的编码习惯,甚至在你的 IDE 代码片段中,让所有新生成的组件都自带 OnPush,你会发现,它不仅仅是一项「性能优化」。
它是一种「架构优化」。
它会「逼迫」你去思考更清晰的数据流,去拥抱不可变性,去优先使用 async 管道,去构建「智能」与「木偶」分离的组件体系。它是一本「修行总纲」,修炼它的过程,本身就会让你掌握一系列更高级的内功心法。
OnPush,就是那块区分 Angular 「开发者」与「架构师」的试金石。当你不再视之为一项「技术」,而是视之为一种「哲学」时,你的 Angular 武学,才算真正地「大成」了。
正如《庄子·养生主》中「庖丁解牛」的故事所揭示的:「彼节者有间,而刀刃者无厚,以无厚入有间,恢恢乎其于游刃必有余地矣。」 (牛的骨节之间有空隙,而刀刃没有厚度,用没有厚度的刀刃插入有空隙的骨节,自然是宽阔得足以让刀刃游刃有余了)。OnPush 的精髓,正是教我们如何去寻找数据流中的「间隙」,以「无厚」的精准,避免「滥砍滥伐」,让变更检测这把「刀」在应用这头「巨牛」中游刃有余,最终达到「三年一新,游刃有余」的境界。