概述
這是一篇偏理論的文章,Angular的變更檢測是這個框架的靈魂,如果我們深入理解這里邊的內(nèi)容,對于優(yōu)化程序,解決性能問題,以及對Angular的深入理解都有很好的幫助,本文涉及到的知識點主要有:
,
,
。
哪些行為會引起Angular的變更檢測?
- DOM事件: click、submit...
- XHR: 從服務(wù)端獲取數(shù)據(jù)
- Timers: setTimeout、setInterval
如上的所示,我們可以發(fā)現(xiàn)
,我們的程序狀態(tài)可能發(fā)生變化,這就是Angular視圖需要更新,需要進行變更檢測的時候。
誰來通知Angular進行變更檢測呢?
Angular有自己的zone,就是我們常見的ngZone,Angular 源碼中有一個東西叫做 ApplicationRef,它監(jiān)聽 NgZone 的 onTurnDone 事件。只要這個事件發(fā)生,它就執(zhí)行 函數(shù),這個函數(shù)執(zhí)行變更檢查,DOM就會更新,看下圖示例,其實zone.js就是
。
變更檢測如何執(zhí)行呢?
在 Angular 中,每個組件都有自己的變更檢測器(change detector)。因為我們可以單獨控制每個組件的變更檢查何時發(fā)生以及如何執(zhí)行,既然每個組件都有自己的 ,并且一個 Angular 應(yīng)用包含著一個組件樹,那么邏輯上我們也有一個
(change detector tree)。這棵樹也可以被看成是一個
,該有向圖的數(shù)據(jù)總是從頂端流向底端(單向數(shù)據(jù)流)。
其實每個視圖(組件)都有一個狀態(tài),也是非常重要的角色,因為根據(jù)它的值(FirstCheck、 ChecksEnabled、Errored、Destoryed),Angular決定是否對視圖及其所有子視圖運行或跳過臟值檢測。如果ChecksEnabled是false或者視圖是Errored或者Destroyed的狀態(tài),變更檢測將會跳過這個視圖以及它的子視圖。
關(guān)于Angular的變更檢測,是有兩種策略,一種是
,另一個是
策略,接下來我們具體闡述一下~
Angular的Default策略
此默認策略,就是我們常提到的臟檢查,它是只要有變化,就從根組件到所有子組件進行檢查(深度優(yōu)先遍歷)
Angular的onPush策略
如上默認的變更檢測,Angular必須每次都檢測所有組件,但是如果我們可以讓Angular僅對應(yīng)用中發(fā)生改變的狀態(tài)進行變更檢測,那樣會更高效,一般我們會從下邊↓兩個方向上著手,實現(xiàn)更加高效且聰明的變更檢測。
- 使用不可變對象(減少對象屬性的對比)
不可變對象保證了這個對象是不可變的,這意味著如果我們使用者不可變對象,同時試圖改變這個對象,那我們總是會得到一個新的引用,如果我們的需求就是只要地址更改才進行變更檢測,這樣我們就不需要對比對象中的屬性了
- 減少檢測組件個數(shù)
如果我們在組件中使用了onPush策略,
變更檢測會在第一次檢查后被禁用,那么只有當輸入屬性(@Input)沒有發(fā)生改變的時候,Angular 會跳過整棵子樹的變更檢查。此變更檢測,只會在當前組件以及其子組件中生效,其相鄰兄弟組件不會受影響。
使用方法如下:
@Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, // 使用Angular的onPush策略
})
在onPush策略下,那些情況可以引起組件的變更檢測?
- 只有當
發(fā)生變化組件才進行變化檢測。
- 有
,組件才進行變化檢測,其他異步事件更換數(shù)據(jù)都不會觸發(fā)變更檢測。(而且即使這個事件是個空的函數(shù)或者操作的不是@Input屬性,也仍然會導(dǎo)致變更檢測的發(fā)生)
-
變更檢測
如何手動觸發(fā)變更檢測呢?
通過引用,可以手動去操作變化檢測。我們可以在組件中的通過依賴注入的方式來獲取該對象:
constructor(
private changeRef: ChangeDetectorRef
){}
此變更檢測對象提供了如下所示的方法
abstract class ChangeDetectorRef {
abstract markForCheck() : void
abstract detectChanges() : void
abstract detach() : void
abstract reattach() : void
abstract checkNoChanges() : void
}
markForCheck()
,那么變化檢測不會再次執(zhí)行,除非手動調(diào)用該方法, 在程序中使用this.changeRef.markForCheck()
變化檢測,如要想要執(zhí)行多次多次,則需要多次的運行這句話。具體的執(zhí)行流程如下:
detectChanges()
首先他的使用與是否使用了onPush無關(guān),他是只在當前視圖和它的子視圖,應(yīng)用場景:明確知道有數(shù)據(jù)的更新,需要Angular執(zhí)行變更檢測的時候使用。
detach()
首先他的使用與是否使用了onPush無關(guān),他是從變化檢測樹中,該組件的變化檢測器將不再執(zhí)行變化檢測,同時其子組件也不會執(zhí)行檢測,除非手動調(diào)用 reattach() 方法
reattach()
首先他的使用與是否使用了onPush無關(guān),他是,使得該組件及其子組件都能執(zhí)行變化檢測,但是如果當前的組件的父組件
(臟檢查)的話,它將
。
使用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ū)別:
示例解析:
當num是5的時候,因為使用了detach(),則此視圖不在進行變更檢測,當我們調(diào)用RecoveryDetection(),執(zhí)行reattach()的時候,頁面的數(shù)據(jù)恢復(fù)遞增顯示,如果我們在這個方法中執(zhí)行的是detectChanges(),頁面的數(shù)據(jù)只會增加一次,就不在更新變化了~
總結(jié)
如果大家想真正了解Angular的變更檢測,一定要動手寫一寫相關(guān)的demo去驗證自己的猜想,否則就像之前的我一樣,看了很多理論,還是不能夠了解,而且還很容易弄混ChangeDetectorRef的屬性~ 希望大家看到這篇文章可以更加清晰點吧~