角度變化檢測和 OnPush 策略
已發表: 2022-03-11你已經開始在你喜歡的所有項目中使用 Angular。 您知道 Angular 必須提供什麼,以及如何利用它來構建出色的 Web 應用程序。 但是,有一些關於 Angular 的東西,了解它們可以讓你更好地在項目中使用 Angular。
數據流是 Angular 幾乎所有事物的核心,變更檢測是值得了解的,因為它可以幫助您更輕鬆地跟踪錯誤,並讓您有機會在處理複雜數據集時進一步優化您的應用程序。
在本文中,您將了解 Angular 如何檢測其數據結構中的更改,以及如何使它們不可變以充分利用 Angular 的更改檢測策略。
Angular中的變化檢測
當您更改任何模型時,Angular 會檢測到更改並立即更新視圖。 這是 Angular 中的變更檢測。 這種機制的目的是確保底層視圖始終與其對應的模型同步。 Angular 的這一核心特性是框架的成功之處,也是 Angular 成為開發現代 Web 應用程序的絕佳選擇的部分原因。
Angular 中的模型可能會因以下任何一種情況而發生變化:
DOM 事件(單擊、懸停等)
AJAX 請求
計時器 (setTimer(), setInterval())
變化檢測器
所有 Angular 應用程序都由一個分層的組件樹組成。 在運行時,Angular 為樹中的每個組件創建一個單獨的更改檢測器類,然後最終形成一個類似於組件層次結構樹的更改檢測器層次結構。
每當觸髮變更檢測時,Angular 就會沿著這棵變更檢測器樹向下走,以確定它們中是否有任何報告了變更。
每次檢測到的變化都會執行一次變化檢測週期,並從根變化檢測器開始,並以順序方式一直向下。 這種順序設計選擇很好,因為它以可預測的方式更新模型,因為我們知道組件數據只能來自其父級。
更改檢測器提供了一種跟踪組件先前和當前狀態及其結構的方法,以便向 Angular 報告更改。
如果 Angular 從變更檢測器獲取報告,它會指示相應的組件重新渲染並相應地更新 DOM。
變更檢測策略
值與引用類型
為了理解什麼是變更檢測策略以及它為什麼起作用,我們必須首先了解 JavaScript 中值類型和引用類型之間的區別。 如果您已經熟悉它的工作原理,則可以跳過本節。
首先,讓我們回顧一下值類型和引用類型及其分類。
值類型
布爾值
空值
不明確的
數字
細繩
為簡單起見,可以想像這些類型只是將它們的值存儲在堆棧內存中(這在技術上是不正確的,但對於本文來說已經足夠了)。 例如,請參見下圖中的堆棧內存及其值。
參考類型
數組
對象
職能
這些類型有點複雜,因為它們在堆棧內存上存儲一個引用,該引用指向它們在堆內存上的實際值。 您可以在下面的示例圖像中看到堆棧內存和堆內存如何協同工作。 我們看到棧內存引用了堆內存中引用類型的實際值。
值類型和引用類型之間的重要區別在於,為了讀取值類型的值,我們只需要查詢堆棧內存,但是為了讀取引用類型的值,我們需要先查詢堆棧內存以獲取引用,然後使用該引用查詢堆內存以定位引用類型的值。
默認策略
正如我們之前所說,Angular 會監控模型的變化,以確保它捕捉到所有的變化。 它將檢查整個應用程序模型的先前狀態和當前狀態之間的任何差異。
Angular 在默認變更檢測策略中提出的問題是:模型中的任何值是否發生了變化? 但是對於引用類型,我們可以實施策略,以便我們可以提出更好的問題。 這就是 OnPush 變更檢測策略的用武之地。
OnPush 策略
OnPush 策略背後的主要思想體現在以下認識中:如果我們將引用類型視為不可變對象,我們可以更快地檢測到值是否發生了變化。 當引用類型是不可變的時,這意味著每次更新時,堆棧內存上的引用都必須更改。 現在我們可以簡單地檢查:引用類型的引用(在堆棧中)是否改變了? 如果是,則僅檢查所有值(在堆上)。 如果這令人困惑,請參考前面的堆棧圖。
OnPush 策略基本上問兩個問題而不是一個問題。 引用類型的引用是否改變了? 如果是,那麼堆內存中的值是否已更改?

例如,假設我們有一個包含 30 個元素的不可變數組,並且我們想知道是否有任何更改。 我們知道,為了對不可變數組進行任何更新,它的引用(在堆棧上)必須改變。 這意味著我們可以首先檢查對數組的引用是否有任何不同,這可能會使我們免於再進行 30 次檢查(在堆中)以確定哪個元素不同。 這稱為 OnPush 策略。
那麼,您可能會問,將引用類型視為不可變意味著什麼? 這意味著我們從不設置引用類型的屬性,而是一起重新分配值。 見下文:
將對象視為可變對象:
static mutable() { var before = {foo: "bar"}; var current = before; current.foo = "hello"; console.log(before === current); // => true }將對象視為不可變的:
static mutable() { var before = {foo: "bar"}; var current = before; current = {foo "hello"}; console.log(before === current); // => false }請注意,在上面的示例中,我們按照慣例將引用類型“視為”不可變,因此最終我們仍在使用可變對象,但只是“假裝”它們是不可變的。
那麼如何為組件實現 OnPush 策略呢? 您需要做的就是在 @Component 註釋中添加changeDetection參數。
import {ChangeDetectionStrategy, Component} from '@angular/core'; @Component({ // ... changeDetection: ChangeDetectionStrategy.OnPush }) export class OnPushComponent { // ... }不可變的.js
如果決定在 Angular 組件上使用 OnPush 策略,那麼強制執行不變性是一個好主意。 這就是 Immutable.js 的用武之地。
Immutable.js 是 Facebook 創建的用於 JavaScript 不變性的庫。 它們有許多不可變的數據結構,例如 List、Map 和 Stack。 出於本文的目的,將對列表和地圖進行說明。 如需更多參考,請在此處查看官方文檔。
為了將 Immutable.js 添加到您的項目中,請確保進入您的終端並運行:
$ npm install immutable --save還要確保在您使用它的組件中從 Immutable.js 導入您正在使用的數據結構。
import {Map, List} from 'immutable';這就是 Immutable.js Map 的使用方式:
var foobar = {foo: "bar"}; var immutableFoobar = Map(foobar); console.log(immutableFooter.get("foo")); // => bar並且,可以使用數組:
var helloWorld = ["Hello", "World!"]; var immutableHelloWorld = List(helloWorld); console.log(immutableHelloWorld.first()); // => Hello console.log(immutableHelloWorld.last()); // => World! helloWorld.push("Hello Mars!"); console.log(immutableHelloWorld.last()); // => Hello Mars!使用 Immutable.js 的缺點
使用 Immutable.js 有幾個主要的可爭論的缺點。
您可能已經註意到,使用它的 API 有點麻煩,傳統的 JavaScript 開發人員可能不喜歡這樣。 一個更嚴重的問題與無法為您的數據模型實現接口有關,因為 Immutable.js 不支持接口。
包起來
你可能會問為什麼 OnPush 策略不是 Angular 的默認策略。 我認為這是因為 Angular 不想強迫 JavaScript 開發人員使用不可變對象。 但是,這並不意味著您被禁止使用它。
如果這是您想在下一個 Web 項目中利用的東西,那麼您現在知道 Angular 可以輕鬆地切換到不同的變更檢測策略。
