本篇文章帶你詳細了解Angular中的變更偵測,跟大家介紹一下Angular 的 DOM 更新機制、變更偵測能解決什麼問題等等。
透過這篇的文章,可以幫助你收穫這些知識:
我們先來看一個最簡單地demo
在按鈕被點擊時,我們改變了元件的name 屬性,就在一瞬間DOM 中也顯示出了改變後的值,這似乎有點「神奇」。
如果緊跟著元素更改的語句之後印出真實DOM 中的innterText,卻發現仍然還是舊的值,可是明明視圖中的值已經改變了,這兩段程式碼中到底發生了什麼事?如果你也對此也疑惑不已,那就和我一起來揭曉這個答案。
我們仔細回想下剛剛發生的事情:
點擊按鈕
值改變
##如果使用原生JS 來寫這段程式碼,那麼點擊按鈕後的視圖肯定不會改變,而在Angular 中卻讓視圖發生了改變。如果你對Angular 有稍微深入的了解,就會知道一個叫做zone.js 的函式庫,仔細翻看就會發現,zone.js 對所有可能發生值改變的事件做了一層處理,例如:
Events:click,mouseover,mouseout,keyup,keydown 等所有的瀏覽器事件
Timer:setTimeout,setInterval
XHR:各類別請求等 停用 zone 後,當我們再次點擊按鈕時,視圖未更新。帶著好奇心,我們找到Angular 原始碼中視圖更新的
關鍵程式碼#這次我們手動在程式碼中呼叫這個方法。
好了,小試牛刀之後,接下裡我們就來仔細探究 Angular 視圖更新的背後到底發生了什麼事。
二、窺探變更偵測的秘密
#1.從一個常見的Error 說起我們先來看這樣一個錯誤,在child 元件的ngOnInit 中更改了父元件parent 的name 值,結果出現了大家一定都遇過的錯誤訊息
#####可是這樣寫並不是每次都會報錯,例如我們去掉子元件child 的輸入屬性,刷新一下,發現同樣的程式碼卻可以執行,父元件的name 可以正常變更。 ###############emmm... 陷入沉思...######也許你跟剛開始學習Angular 時的我一樣,在stackoverflow 裡搜尋這個問題,複製了個自己也不知道為什麼能起作用的程式碼就直接貼了上去,後面再遇到這個問題時,繼續在stackoverflow 裡搜尋和複製貼上,如此反覆...###隨著時間的推移,精通各種CRUD 的你越來越不滿足於這種面向stackoverflow 編程的自己,開始在社區、文檔、論壇上不停的查找問題的答案,但是看完他們的答案和文章,好像只知道了有個叫做變更檢測的東西,但是具體是怎麼導致了這個bug ,卻支支吾吾的說不太清楚,如果你也和我一樣對上述經歷深有體會,那就繼續向下探尋真相吧!
當我們在model 中改變資料時,框架層需要知道:
React 中的Virtual Dom 大家一定都不陌生,React 透過對比DOM 的新狀態與舊狀態來決定更新哪一部分dom,而不是更新所有的dom,這也是Angular 中變更偵測(change detection)的異曲同工之處。
整個Angular 應用程式是個元件樹,不可能任意一個元件中的改變觸發所有元件的更新,這樣效率太低也太耗時,例如使用者更改了某個button 的狀態,那麼最理想的做法是只更新這個button 的樣式或文字,而不是整個應用程式全部更新一遍,變更偵測的目的也就是為此。
預設(ChangeDetectionStrategy.Default
),父元件的變更偵測發生時,子元件也會觸發變更偵測。
(CD
即為changeDetection
)
每次變更偵測時,都會比較新舊狀態,如果兩次變更偵測(開發環境下)的結果不一致就會報錯,例如:
Expression has changed after it was checked
<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 = 'child'好了,到這裡我們已經明白了這種錯誤發生的真正原因,但是我還是要提醒一下,這種錯誤只會在開發環境下觸發,生產環境下會調用
enableProdMode() ,變更偵測次數會從2 降到1,這部分在
Angular 原始碼當中也有描述。
ChangeDetectionStrategy 預設為Default,也就是父元件的CD 會觸發子元件的CD,但是很顯然有些情況下我們可以自行判斷出某些子元件在父組件CD 時並不用觸發,而
OnPush
则是 Angular 为开发者提供的一便捷操作方式。
用动图来表示就是:查看链接
知名的 Angular 开源组件库 ng-zorro 就使用了大量的 OnPush 策略,这也是 Angular 性能优化的方法之一。
Angular 给每个组件都关联了一份组件视图,通过 ChangeDetectorRef
可以拿到相关联的视图,在定义中我们可以看到:
export declare abstract class ChangeDetectorRef { abstract checkNoChanges(): void; abstract detach(): void; abstract detectChanges(): void; abstract markForCheck(): void; abstract reattach(): void; }
观察下面的动图,被 detached
的组件将不会被检查变更。
而 reattach
则可以让被 detached
的组件重新可以被检测到变更。
reattach
只会重新启用对当前组件的变更检测,但是如果父组件没有启动变更检测,那么 reattach
并不会起作用,而 markForCheck
可以很好地解决这个问题。
这一点在 ng-zorro 的源码中可以了解一二。
例如在 nz-anchor 组件中更改 nz-anchor-link 组件的 active 属性时,由于本身 ChangeDetectionStrategy
为 OnPush
,那么就需要激活 markForCheck 来重新启用检测。具体写法可以查看 github 中的源代码。
用动图来展示则是这样,注意观察设置了 MFC 的前后变化
这个方法如同字面意思一样很好理解,就是触发一次变更检测啦,还记得本文中的第一个例子吗,我们不手动触发 tick()
,而是触发 detechtChanges()
也是可以达到效果的。
到这里,我相信大家已经基本弄明白了 Angular 变更检测,如果有任何疑问,欢迎在评论区交流讨论~
在撰写这篇文章时,笔者参(fu)考(zhi)了大量的社区文章和讨论,一方面是感慨如此重要的概念在 Angular 中文社区中却只有零星几篇相关介绍的文章,另一方面是看到了虽然国内 Angular 开发者虽然数量远少于 React 和 Vue,却依然非常热情的贡献自己的知识和见解来为 Angular 中文社区添砖加瓦,作为已使用 Angular 半年多的开发者,深深感受到 Google 的工程美学。
大而全且不失优雅,是笔者对 Angular 这款 Web 框架的最大感受,感谢开源社区中的各位开发者们~
对于文中描述错误的地方,还望大佬们批评斧正~
原文地址:https://juejin.cn/post/6844904079878012935
作者:LangWalker
更多编程相关知识,请访问:编程入门!!
以上是帶你詳細了解Angular中的變更檢測的詳細內容。更多資訊請關注PHP中文網其他相關文章!