溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Angular中如何實現(xiàn)變更檢測

發(fā)布時間:2021-08-11 11:06:45 來源:億速云 閱讀:141 作者:小新 欄目:web開發(fā)

小編給大家分享一下Angular中如何實現(xiàn)變更檢測,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

一、Angular 的 DOM 更新機制

我們先來看一個最簡單地 demo

Angular中如何實現(xiàn)變更檢測

在按鈕被點擊時,我們改變了組件的 name 屬性,就在一瞬間 DOM 中也顯示出了改變后的值,這似乎有些“神奇”。

Angular中如何實現(xiàn)變更檢測

如果緊跟著元素更改的語句之后打印出真實 DOM 中的 innterText,卻發(fā)現(xiàn)仍然還是舊的值,可是明明視圖中的值已經(jīng)改變了,這兩段代碼中到底發(fā)生了什么呢?如果你也對此也疑惑不已,那么就和我一起來揭曉這個答案。

我們仔細回憶下剛剛發(fā)生的事情:

  • 點擊按鈕

  • 值改變

如果使用原生 JS 來編寫這段代碼,那么點擊按鈕后的視圖肯定不會發(fā)生改變,而在 Angular 中卻讓視圖發(fā)生了改變。如果你對 Angular 有稍微深入的了解,就會知道一個叫做 zone.js 的庫,仔細翻看就會發(fā)現(xiàn),zone.js 對所有可能發(fā)生值改變的事件做了一層處理,比如:

  • Events:click,mouseover,mouseout,keyup,keydown 等所有的瀏覽器事件

  • Timer:setTimeout,setInterval

  • XHR:各類請求等

Angular 還為我們提供了禁用 zone.js 的方法。

Angular中如何實現(xiàn)變更檢測

Angular中如何實現(xiàn)變更檢測

禁用 zone 后,當我們再次點擊按鈕時,視圖未更新。

帶著好奇心,我們找到 Angular 源碼中視圖更新的關鍵代碼

Angular中如何實現(xiàn)變更檢測

這一次我們手動在代碼調(diào)用這個方法。

Angular中如何實現(xiàn)變更檢測

Angular中如何實現(xiàn)變更檢測

果然和預料中的一樣!視圖更新了,更讓人驚喜是,打印出來的 innerText 也更新了!

到這里,我們得出了一個結論,DOM 的更新依賴于 tick() 的觸發(fā),zone.js 幫助開發(fā)者省去了手動觸發(fā)的操作

好了,小試牛刀之后,接下里我們就來仔細探究 Angular 視圖更新的背后到底發(fā)生了什么。

二、窺探變更檢測的秘密

1.從一個常見的 Error 說起

我們先來看這樣一處錯誤,在 child 組件的 ngOnInit 中更改了父組件 parent 的 name 值,結果出現(xiàn)了大家一定都遇到過的錯誤信息

Angular中如何實現(xiàn)變更檢測

Angular中如何實現(xiàn)變更檢測

可是這樣寫并不是每次都會報錯,例如我們?nèi)サ糇咏M件 child 的輸入屬性,刷新一下,發(fā)現(xiàn)同樣的代碼卻可以運行,父組件的 name 可以被正常更改。

Angular中如何實現(xiàn)變更檢測

emmm... 陷入沉思...

也許你和剛開始學習 Angular 時的我一樣,在 stackoverflow 里搜索這個問題,復制了個自己也不知道為什么能起作用的代碼就直接粘貼了上去,后面再遇到這個問題時,繼續(xù)在 stackoverflow 里搜索和復制粘貼,如此反復...

隨著時間的推移,精通各種 CRUD 的你越來越不滿足于這種面向 stackoverflow 編程的自己,開始在社區(qū)、文檔、論壇上不停的查找問題的答案,但是看完他們的回答和文章,好像只知道了有個叫做變更檢測的東西,但是具體是怎么導致了這個 bug ,卻支支吾吾的說不太清楚,如果你也和我一樣對上述經(jīng)歷深有體會,那么就繼續(xù)向下探尋真相吧!

2.說了半天的變更檢測到底是什么?

當我們在 model 中改變數(shù)據(jù)時,框架層需要知道:

  • model 哪里發(fā)生了改變

  • view 中哪里需要更新

React 中的 Virtual Dom 大家一定都不陌生,React 通過對比 DOM 的新狀態(tài)與舊狀態(tài)來決定更新哪一部分 dom,而不是更新所有的 dom,這也是 Angular 中變更檢測(change detection)的異曲同工之處。

整個 Angular 應用是個組件樹,不可能任意一個組件中的改變都觸發(fā)所有組件的更新,這樣效率太低也太耗時,例如用戶更改了某個 button 的狀態(tài),那么最理想的做法是只更新這個 button 的樣式或文字,而不是整個應用全部更新一遍,變更檢測的目的也就是為此。

默認情況下(ChangeDetectionStrategy.Default),父組件的變更檢測發(fā)生時,子組件也會觸發(fā)變更檢測。

Angular中如何實現(xiàn)變更檢測

(CD 即為 changeDetection )

每次變更檢測時,都會比較新舊狀態(tài),如果兩次變更檢測(開發(fā)環(huán)境下)的結果不一致就會報錯,例如:

Expression has changed after it was checked

這也就解釋了為什么在子組件中更改了父組件的值會報錯。

但是!在前面的兩個例子中我們都在子組件中更改了父組件的值,只有第一個報錯,第二個是可以正常更新的,如果你也同樣很疑惑這中間真正的差異點在哪里,那么接著往下閱讀吧~

3.問題的關鍵 — 檢測順序 Detection Sequence

先上結論:

  • 更新所有子組件的綁定屬性

  • 調(diào)用所有子組件的 OnChanges,OnInit,DoCheck,AfterContentInit 生命周期鉤子

  • 更新當前組件的 DOM

  • 子組件觸發(fā)變更檢測

  • 調(diào)用所有子組件的的 AfterViewInit 的生命周期鉤子

這里我們不關注于太細的細節(jié)(不用好奇為什么是這樣的順序,只要記住 Angular 里就是這樣設定的就可以了,如果有大佬想談談 Angular 在這部分的設計思想,歡迎在評論區(qū)留言探討~)

第一個例子中,父組件 parent 給子組件 child 傳入了輸入屬性 name,且子組件在 ngOnInit 中更新了父組件的 name 屬性,也就是說這段代碼**違背了檢測順序(**在順序的第二步中操作了第一步)!

<p>{{ name }}<p>
<child [name]="name"></child>

而在第二個例子中,就算子組件在 ngOnInit 中也更新了父組件的 name 屬性,但是由于父組件parent 中沒有給子組件 child 綁定輸入屬性 name,不會出現(xiàn)與違背變更檢測隊列順序的情況,所以就可以正常運行。

<p>{{ name }}<p>
<child></child>

這個時候再去看看 stackoverflow 上的高贊回答 是不是就清晰明了很多,按照上述的檢測順序,我們會發(fā)現(xiàn)只要父組件中對子組件做了屬性綁定,不管是在 OnChanges,OnInit,DoCheck,AfterContentInit 和 AfterViewInit 中的任意一個聲明周期鉤子中執(zhí)行下述代碼都會報錯。

this.parentCmpt.name = 'child'

好了,到這里我們已經(jīng)明白了這種錯誤發(fā)生的真正原因,但是我還是要提醒一下,這種錯誤只會在開發(fā)環(huán)境下觸發(fā),生產(chǎn)環(huán)境下會調(diào)用 enableProdMode() ,變更檢測次數(shù)會從 2 降到 1,這部分在 Angular 源碼當中也有描述。

Angular中如何實現(xiàn)變更檢測

當然你不能因為這個 bug 就強制在開發(fā)環(huán)境下使用生產(chǎn)模式...

4.大家常說的 ChangeDetectionStrategy.OnPush 又是什么?

ChangeDetectionStrategy 默認為 Default,也就是父組件的 CD 會觸發(fā)子組件的 CD,但是很顯然有些情況下我們可以自行判斷出某些子組件在父組件 CD 時并不用觸發(fā),而 OnPush

則是 Angular 為開發(fā)者提供的一便捷操作方式。

Angular中如何實現(xiàn)變更檢測

用動圖來表示就是:查看鏈接

Angular中如何實現(xiàn)變更檢測

知名的 Angular 開源組件庫 ng-zorro 就使用了大量的 OnPush 策略,這也是 Angular 性能優(yōu)化的方法之一。

Angular中如何實現(xiàn)變更檢測

三、再深入了解一些

Angular 給每個組件都關聯(lián)了一份組件視圖,通過 ChangeDetectorRef 可以拿到相關聯(lián)的視圖,在定義中我們可以看到:

export declare abstract class ChangeDetectorRef {
    abstract checkNoChanges(): void;
    abstract detach(): void;
    abstract detectChanges(): void;
    abstract markForCheck(): void;
    abstract reattach(): void;
}

1.detach 和 reattach

觀察下面的動圖,被 detached 的組件將不會被檢查變更。

Angular中如何實現(xiàn)變更檢測

reattach 則可以讓被 detached 的組件重新可以被檢測到變更。

2.markForCheck

reattach 只會重新啟用對當前組件的變更檢測,但是如果父組件沒有啟動變更檢測,那么 reattach 并不會起作用,而 markForCheck 可以很好地解決這個問題。

這一點在 ng-zorro 的源碼中可以了解一二。

例如在 nz-anchor 組件中更改 nz-anchor-link 組件的 active 屬性時,由于本身 ChangeDetectionStrategyOnPush ,那么就需要激活 markForCheck 來重新啟用檢測。具體寫法可以查看 github 中的源代碼。

  • nz-anchor.component.ts

  • nz-anchor-link.component.ts

用動圖來展示則是這樣,注意觀察設置了 MFC 的前后變化

Angular中如何實現(xiàn)變更檢測

3.detectChanges

這個方法如同字面意思一樣很好理解,就是觸發(fā)一次變更檢測啦,還記得本文中的第一個例子嗎,我們不手動觸發(fā) tick() ,而是觸發(fā) detechtChanges() 也是可以達到效果的。

Angular中如何實現(xiàn)變更檢測

Angular中如何實現(xiàn)變更檢測

以上是“Angular中如何實現(xiàn)變更檢測”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業(yè)資訊頻道!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI