首頁  >  文章  >  web前端  >  帶你詳細了解Angular中的變更檢測

帶你詳細了解Angular中的變更檢測

青灯夜游
青灯夜游轉載
2021-08-11 10:48:562338瀏覽

本篇文章帶你詳細了解Angular中的變更偵測,跟大家介紹一下Angular 的 DOM 更新機制、變更偵測能解決什麼問題等等。

帶你詳細了解Angular中的變更檢測

透過這篇的文章,可以幫助你收穫這些知識:

  • Angular 的 DOM 更新是如何做到的? 【相關教學推薦:《angular教學》】
  • 變更偵測解決了什麼問題?
  • 更深入了解變更偵測,熟悉它在Angular 原始碼中的定義以及在ng-zorro 中的使用
  • 以及一點個人感悟

一、Angular 的DOM 更新機制

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

帶你詳細了解Angular中的變更檢測

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

帶你詳細了解Angular中的變更檢測

如果緊跟著元素更改的語句之後印出真實DOM 中的innterText,卻發現仍然還是舊的值,可是明明視圖中的值已經改變了,這兩段程式碼中到底發生了什麼事?如果你也對此也疑惑不已,那就和我一起來揭曉這個答案。

我們仔細回想下剛剛發生的事情:

  • 點擊按鈕

  • 值改變

##如果使用原生JS 來寫這段程式碼,那麼點擊按鈕後的視圖肯定不會改變,而在Angular 中卻讓視圖發生了改變。如果你對Angular 有稍微深入的了解,就會知道一個叫做zone.js 的函式庫,仔細翻看就會發現,zone.js 對所有可能發生值改變的事件做了一層處理,例如:

帶你詳細了解Angular中的變更檢測Events:click,mouseover,mouseout,keyup,keydown 等所有的瀏覽器事件

Timer:setTimeout,setInterval帶你詳細了解Angular中的變更檢測

XHR:各類別請求等

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

帶你詳細了解Angular中的變更檢測

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

帶你詳細了解Angular中的變更檢測帶著好奇心,我們找到Angular 原始碼中視圖更新的

關鍵程式碼

7-帶你詳細了解Angular中的變更檢測

#這次我們手動在程式碼中呼叫這個方法。

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

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

好了,小試牛刀之後,接下裡我們就來仔細探究 Angular 視圖更新的背後到底發生了什麼事。

帶你詳細了解Angular中的變更檢測二、窺探變更偵測的秘密

帶你詳細了解Angular中的變更檢測

#1.從一個常見的Error 說起

我們先來看這樣一個錯誤,在child 元件的ngOnInit 中更改了父元件parent 的name 值,結果出現了大家一定都遇過的錯誤訊息帶你詳細了解Angular中的變更檢測

#####可是這樣寫並不是每次都會報錯,例如我們去掉子元件child 的輸入屬性,刷新一下,發現同樣的程式碼卻可以執行,父元件的name 可以正常變更。 ###############emmm... 陷入沉思...######也許你跟剛開始學習Angular 時的我一樣,在stackoverflow 裡搜尋這個問題,複製了個自己也不知道為什麼能起作用的程式碼就直接貼了上去,後面再遇到這個問題時,繼續在stackoverflow 裡搜尋和複製貼上,如此反覆...###

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

2.說了半天的變更偵測到底是什麼?

當我們在model 中改變資料時,框架層需要知道:

  • model 哪裡發生了改變
  • view 中哪裡需要更新

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

整個Angular 應用程式是個元件樹,不可能任意一個元件中的改變觸發所有元件的更新,這樣效率太低也太耗時,例如使用者更改了某個button 的狀態,那麼最理想的做法是只更新這個button 的樣式或文字,而不是整個應用程式全部更新一遍,變更偵測的目的也就是為此。

預設(ChangeDetectionStrategy.Default),父元件的變更偵測發生時,子元件也會觸發變更偵測。

帶你詳細了解Angular中的變更檢測

(CD 即為changeDetection )

每次變更偵測時,都會比較新舊狀態,如果兩次變更偵測(開發環境下)的結果不一致就會報錯,例如:

Expression has changed after it was checked

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

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

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

先上結論:

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

  • 呼叫所有子元件的OnChanges,OnInit,DoCheck,AfterContentInit 生命週期鉤子

  • #更新目前元件的DOM

  • 子元件觸發變更檢測

  • #呼叫所有子元件的的AfterViewInit 的生命週期鉤子

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

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

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

而在第二個範例中,就算子元件在ngOnInit 中也更新了父元件的name 屬性,但由於父元件parent 中沒有給子元件child 綁定輸入屬性name,不會出現與違背變更偵測佇列順序的情況,所以就可以正常運作。

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

這時候再去看看

stackoverflow 上的高讚回答 是不是就清晰明了很多,按照上述的檢測順序,我們會發現只要父組件中對子組件做了屬性綁定,不管是在OnChanges,OnInit,DoCheck,AfterContentInit 和AfterViewInit 中的任一個宣告週期鉤子中執行下述程式碼都會報錯。

this.parentCmpt.name = &#39;child&#39;

好了,到這裡我們已經明白了這種錯誤發生的真正原因,但是我還是要提醒一下,這種錯誤只會在開發環境下觸發,生產環境下會調用

enableProdMode() ,變更偵測次數會從2 降到1,這部分在Angular 原始碼當中也有描述。

帶你詳細了解Angular中的變更檢測

當然你不能因為這個bug 就強制在開發環境下使用生產模式...

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

ChangeDetectionStrategy 預設為Default,也就是父元件的CD 會觸發子元件的CD,但是很顯然有些情況下我們可以自行判斷出某些子元件在父組件CD 時並不用觸發,而OnPush

则是 Angular 为开发者提供的一便捷操作方式。

1帶你詳細了解Angular中的變更檢測

用动图来表示就是:查看链接

1帶你詳細了解Angular中的變更檢測

知名的 Angular 开源组件库 ng-zorro 就使用了大量的 OnPush 策略,这也是 Angular 性能优化的方法之一。

1帶你詳細了解Angular中的變更檢測

三、再深入了解一些

Angular 给每个组件都关联了一份组件视图,通过 ChangeDetectorRef 可以拿到相关联的视图,在定义中我们可以看到:

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中的變更檢測

reattach 则可以让被 detached 的组件重新可以被检测到变更。

2.markForCheck

reattach 只会重新启用对当前组件的变更检测,但是如果父组件没有启动变更检测,那么 reattach 并不会起作用,而 markForCheck 可以很好地解决这个问题。

这一点在 ng-zorro 的源码中可以了解一二。

例如在 nz-anchor 组件中更改 nz-anchor-link 组件的 active 属性时,由于本身 ChangeDetectionStrategyOnPush ,那么就需要激活 markForCheck 来重新启用检测。具体写法可以查看 github 中的源代码。

用动图来展示则是这样,注意观察设置了 MFC 的前后变化

帶你詳細了解Angular中的變更檢測

3.detectChanges

这个方法如同字面意思一样很好理解,就是触发一次变更检测啦,还记得本文中的第一个例子吗,我们不手动触发 tick() ,而是触发 detechtChanges() 也是可以达到效果的。

1帶你詳細了解Angular中的變更檢測

帶你詳細了解Angular中的變更檢測

四、最后

到这里,我相信大家已经基本弄明白了 Angular 变更检测,如果有任何疑问,欢迎在评论区交流讨论~

在撰写这篇文章时,笔者参(fu)考(zhi)了大量的社区文章和讨论,一方面是感慨如此重要的概念在 Angular 中文社区中却只有零星几篇相关介绍的文章,另一方面是看到了虽然国内 Angular 开发者虽然数量远少于 React 和 Vue,却依然非常热情的贡献自己的知识和见解来为 Angular 中文社区添砖加瓦,作为已使用 Angular 半年多的开发者,深深感受到 Google 的工程美学。

大而全且不失优雅,是笔者对 Angular 这款 Web 框架的最大感受,感谢开源社区中的各位开发者们~

对于文中描述错误的地方,还望大佬们批评斧正~

原文地址:https://juejin.cn/post/6844904079878012935

作者:LangWalker

更多编程相关知识,请访问:编程入门!!

以上是帶你詳細了解Angular中的變更檢測的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.im。如有侵權,請聯絡admin@php.cn刪除