深入理解Angular的變更檢測

概述

這是一篇偏理論的文章,Angular的變更檢測是這個框架的靈魂,如果我們深入理解這里邊的內(nèi)容,對于優(yōu)化程序,解決性能問題,以及對Angular的深入理解都有很好的幫助,本文涉及到的知識點主要有: \color{#13c078}{Angular的} \color{#13c078}{變更檢測策略}\color{#13c078}{手動變更檢測方法},\color{#13c078}{Angular的} \color{#13c078}{ChangeDetectorRef對象詳解}

哪些行為會引起Angular的變更檢測?

  • DOM事件: click、submit...
  • XHR: 從服務(wù)端獲取數(shù)據(jù)
  • Timers: setTimeout、setInterval

如上的所示,我們可以發(fā)現(xiàn)\color{#13c078}{只要發(fā)生異步操作},我們的程序狀態(tài)可能發(fā)生變化,這就是Angular視圖需要更新,需要進行變更檢測的時候。

誰來通知Angular進行變更檢測呢?

Angular有自己的zone,就是我們常見的ngZone,Angular 源碼中有一個東西叫做 ApplicationRef,它監(jiān)聽 NgZone 的 onTurnDone 事件。只要這個事件發(fā)生,它就執(zhí)行\color{#13c078}{tick()} 函數(shù),這個函數(shù)執(zhí)行變更檢查,DOM就會更新,看下圖示例,其實zone.js就是\color{#13c078}{幫助開發(fā)者省去了手動觸發(fā)操作}。

屏幕快照 2021-08-03 下午1.22.21.png

變更檢測如何執(zhí)行呢?

在 Angular 中,每個組件都有自己的變更檢測器(change detector)。因為我們可以單獨控制每個組件的變更檢查何時發(fā)生以及如何執(zhí)行,既然每個組件都有自己的\color{#13c078}{變更檢查器} ,并且一個 Angular 應(yīng)用包含著一個組件樹,那么邏輯上我們也有一個\color{#13c078}{變更檢測樹}(change detector tree)。這棵樹也可以被看成是一個\color{#13c078}{有向圖},該有向圖的數(shù)據(jù)總是從頂端流向底端(單向數(shù)據(jù)流)。
其實每個視圖(組件)都有一個狀態(tài),也是非常重要的角色,因為根據(jù)它的值(FirstCheck、 ChecksEnabled、Errored、Destoryed),Angular決定是否對視圖及其所有子視圖運行或跳過臟值檢測。如果ChecksEnabled是false或者視圖是Errored或者Destroyed的狀態(tài),變更檢測將會跳過這個視圖以及它的子視圖。

關(guān)于Angular的變更檢測,是有兩種策略,一種是\color{#13c078}{ChangeDetectionStrategy.Default策略},另一個是\color{#13c078}{ChangeDetectionStrategy.onPush}策略,接下來我們具體闡述一下~

Angular的Default策略

此默認策略,就是我們常提到的臟檢查,它是只要有變化,就從根組件所有子組件進行檢查(深度優(yōu)先遍歷)

屏幕快照 2021-08-03 下午2.08.18.png

Angular的onPush策略

如上默認的變更檢測,Angular必須每次都檢測所有組件,但是如果我們可以讓Angular僅對應(yīng)用中發(fā)生改變的狀態(tài)進行變更檢測,那樣會更高效,一般我們會從下邊↓兩個方向上著手,實現(xiàn)更加高效且聰明的變更檢測。

  • 使用不可變對象(減少對象屬性的對比)

不可變對象保證了這個對象是不可變的,這意味著如果我們使用者不可變對象,同時試圖改變這個對象,那我們總是會得到一個新的引用,如果我們的需求就是只要地址更改才進行變更檢測,這樣我們就不需要對比對象中的屬性了

  • 減少檢測組件個數(shù)

如果我們在組件中使用了onPush策略,變更檢測會在第一次檢查后被禁用,那么只有當輸入屬性(@Input)沒有發(fā)生改變的時候,Angular 會跳過整棵子樹的變更檢查。此變更檢測,只會在當前組件以及其子組件中生效,其相鄰兄弟組件不會受影響。

屏幕快照 2021-08-02 下午7.41.09.png

使用方法如下:

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush, // 使用Angular的onPush策略
})

在onPush策略下,那些情況可以引起組件的變更檢測?

  1. 只有當\color{#13c078}{輸入數(shù)據(jù)(即@Input)的引用}發(fā)生變化組件才進行變化檢測。
  2. \color{#13c078}{DOM事件觸發(fā)時},組件才進行變化檢測,其他異步事件更換數(shù)據(jù)都不會觸發(fā)變更檢測。(而且即使這個事件是個空的函數(shù)或者操作的不是@Input屬性,也仍然會導(dǎo)致變更檢測的發(fā)生)
  3. \color{#13c078}{手動觸發(fā)}變更檢測

如何手動觸發(fā)變更檢測呢?

通過引用\color{#13c078}{變化檢測對象ChangeDetectorRef},可以手動去操作變化檢測。我們可以在組件中的通過依賴注入的方式來獲取該對象:

constructor(
   private changeRef: ChangeDetectorRef
){}

此變更檢測對象提供了如下所示的方法

abstract class ChangeDetectorRef {

abstract markForCheck() : void 

abstract detectChanges() : void

abstract detach() : void

abstract reattach() : void 

abstract checkNoChanges() : void

}
markForCheck()

\color{#13c078}{在使用OnPush策略時},那么變化檢測不會再次執(zhí)行,除非手動調(diào)用該方法, 在程序中使用this.changeRef.markForCheck()\color{#13c078}{只能引起一次}變化檢測,如要想要執(zhí)行多次多次,則需要多次的運行這句話。具體的執(zhí)行流程如下:

屏幕快照 2021-08-02 下午7.40.02.png
detectChanges()

首先他的使用與是否使用了onPush無關(guān),他是只在當前視圖和它的子視圖\color{#13c078}{只運行一次變更檢測},應(yīng)用場景:明確知道有數(shù)據(jù)的更新,需要Angular執(zhí)行變更檢測的時候使用。

屏幕快照 2021-08-03 上午11.21.25.png
detach()

首先他的使用與是否使用了onPush無關(guān),他是從變化檢測樹中\color{#13c078}{分離變化檢測器},該組件的變化檢測器將不再執(zhí)行變化檢測,同時其子組件也不會執(zhí)行檢測,除非手動調(diào)用 reattach() 方法

reattach()

首先他的使用與是否使用了onPush無關(guān),他是\color{#13c078}{重新添加已分離的變化檢測器},使得該組件及其子組件都能執(zhí)行變化檢測,但是如果當前的組件的父組件\color{#13c078}{沒有啟用變更檢測}(臟檢查)的話,它將\color{#13c078}{不起作用}。

使用async pipe

當父組件的輸入屬性是用observable,那么除了使用this.changeRef.markForCheck()來進行變更檢測,我們還可以在子組件中使用async pipe, 發(fā)出一個新值時,異步管道會將組件標記為要檢查更改(其實也是調(diào)用了 this.changeRef.markForCheck())

<p>{{addCount | async}}</p>

ChangeDetectorRef中易混方法對比

markForCheck()與detectChanges()的對比?
  • markForCheck():

不會真正觸發(fā)變成檢測,而是將根組件標記為需要檢查
② 基本上只在Angular的檢測策略被設(shè)置成OnPush的時候會被使用到
③ 他的范圍是從當前組件向上擴展到根組件

  • detectChanges():

會真正觸發(fā)Angular的change detection
② 當數(shù)據(jù)更新,但是視圖沒有更新的時候,會用到這個方法,是用于手動觸發(fā)變更檢測的方法之一,當然微任務(wù)的定時器啥的也會觸發(fā)變更檢測
③ 更改檢測將針對當前組件以及所有子組件
④ 可以與detach使用,完成局部的更新

reattach()與detectChanges()的對比?
  • detectChanges()

只會讓變成檢測手動執(zhí)行一次

  • reattach()

是將視圖加回去,變更檢測會根據(jù)業(yè)務(wù)中數(shù)據(jù)的變化一直進行變化,相當于完全還原最原本的變更檢測

通過一個示例更好的理解這兩個方法的區(qū)別:


屏幕快照 2021-08-03 下午1.29.16.png

示例解析:

當num是5的時候,因為使用了detach(),則此視圖不在進行變更檢測,當我們調(diào)用RecoveryDetection(),執(zhí)行reattach()的時候,頁面的數(shù)據(jù)恢復(fù)遞增顯示,如果我們在這個方法中執(zhí)行的是detectChanges(),頁面的數(shù)據(jù)只會增加一次,就不在更新變化了~

總結(jié)

如果大家想真正了解Angular的變更檢測,一定要動手寫一寫相關(guān)的demo去驗證自己的猜想,否則就像之前的我一樣,看了很多理論,還是不能夠了解,而且還很容易弄混ChangeDetectorRef的屬性~ 希望大家看到這篇文章可以更加清晰點吧~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容