Winkeländerungserkennung und die OnPush-Strategie
Veröffentlicht: 2022-03-11Sie haben begonnen, Angular für all Ihre Lieblingsprojekte zu verwenden. Sie wissen, was Angular zu bieten hat und wie Sie es nutzen können, um fantastische Web-Apps zu erstellen. Aber es gibt bestimmte Dinge über Angular, und wenn Sie sie kennen, können Sie Angular besser für Ihre Projekte verwenden.
Da der Datenfluss im Mittelpunkt fast aller Angular-Dinge steht, ist die Änderungserkennung etwas Wissenswertes, da es Ihnen hilft, Fehler viel einfacher aufzuspüren und Ihnen die Möglichkeit gibt, Ihre Apps bei der Arbeit mit einem komplexen Datensatz weiter zu optimieren.
In diesem Artikel erfahren Sie, wie Angular Änderungen in seinen Datenstrukturen erkennt und wie Sie sie unveränderlich machen können, um das Beste aus den Änderungserkennungsstrategien von Angular herauszuholen.
Änderungserkennung in Angular
Wenn Sie eines Ihrer Modelle ändern, erkennt Angular die Änderungen und aktualisiert die Ansichten sofort. Dies ist die Änderungserkennung in Angular. Der Zweck dieses Mechanismus besteht darin, sicherzustellen, dass die zugrunde liegenden Ansichten immer mit ihren entsprechenden Modellen synchronisiert sind. Diese Kernfunktion von Angular macht das Framework zum Ticken und ist teilweise der Grund, warum Angular eine gute Wahl für die Entwicklung moderner Web-Apps ist.
Ein Modell in Angular kann sich aufgrund eines der folgenden Szenarien ändern:
DOM-Ereignisse (klicken, darüber fahren usw.)
AJAX-Anfragen
Timer (setTimer(), setInterval())
Detektoren wechseln
Alle Angular-Apps bestehen aus einem hierarchischen Baum von Komponenten. Zur Laufzeit erstellt Angular für jede Komponente im Baum eine separate Änderungsdetektorklasse, die dann schließlich eine Hierarchie von Änderungsdetektoren ähnlich dem Hierarchiebaum der Komponenten bildet.
Immer wenn eine Änderungserkennung ausgelöst wird, durchläuft Angular diesen Baum von Änderungsdetektoren, um festzustellen, ob einer von ihnen Änderungen gemeldet hat.
Der Änderungserkennungszyklus wird immer einmal für jede erkannte Änderung durchgeführt und beginnt beim Wurzeländerungsdetektor und geht den ganzen Weg nach unten in einer sequentiellen Weise. Diese sequenzielle Entwurfsauswahl ist gut, weil sie das Modell auf vorhersagbare Weise aktualisiert, da wir wissen, dass Komponentendaten nur von ihrem übergeordneten Element stammen können.
Die Änderungsdetektoren bieten eine Möglichkeit, den vorherigen und aktuellen Status der Komponente sowie ihre Struktur zu verfolgen, um Änderungen an Angular zu melden.
Wenn Angular den Bericht von einem Änderungsdetektor erhält, weist es die entsprechende Komponente an, das DOM erneut zu rendern und entsprechend zu aktualisieren.
Erkennungsstrategien ändern
Wert vs. Referenztypen
Um zu verstehen, was eine Änderungserkennungsstrategie ist und warum sie funktioniert, müssen wir zunächst den Unterschied zwischen Werttypen und Referenztypen in JavaScript verstehen. Wenn Sie bereits wissen, wie das funktioniert, können Sie diesen Abschnitt überspringen.
Sehen wir uns zunächst Werttypen und Referenztypen und ihre Klassifizierungen an.
Werttypen
Boolesch
Null
Nicht definiert
Anzahl
Schnur
Der Einfachheit halber kann man sich vorstellen, dass diese Typen ihren Wert einfach im Stapelspeicher ablegen (was technisch nicht stimmt, aber für diesen Artikel ausreicht). Sehen Sie sich zum Beispiel den Stack-Speicher und seine Werte im Bild unten an.
Referenztypen
Arrays
Objekte
Funktionen
Diese Typen sind etwas komplizierter, da sie eine Referenz im Stack-Speicher speichern, die auf ihren tatsächlichen Wert im Heap-Speicher zeigt. Im folgenden Beispielbild können Sie sehen, wie Stack-Speicher und Heap-Speicher zusammenarbeiten. Wir sehen, dass der Stack-Speicher auf die tatsächlichen Werte des Referenztyps im Heap-Speicher verweist.
Die wichtige Unterscheidung zwischen Werttypen und Referenztypen besteht darin, dass wir zum Lesen des Werts des Werttyps nur den Stapelspeicher abfragen müssen, aber um den Wert eines Referenztyps zu lesen, müssen wir dies zuerst tun den Stack-Speicher abfragen, um die Referenz zu erhalten, und dann diese Referenz verwenden, um den Heap-Speicher abzufragen, um den Wert des Referenztyps zu lokalisieren.
Standardstrategie
Wie bereits erwähnt, überwacht Angular Änderungen am Modell, um sicherzustellen, dass alle Änderungen erfasst werden. Es wird nach Unterschieden zwischen dem vorherigen Zustand und dem aktuellen Zustand des gesamten Anwendungsmodells suchen.
Die Frage, die Angular in der Standardstrategie zur Erkennung von Änderungen stellt, lautet: Hat sich ein Wert im Modell geändert? Aber für einen Referenztyp können wir Strategien implementieren, damit wir eine bessere Frage stellen können. Hier kommt die OnPush-Änderungserkennungsstrategie ins Spiel.

OnPush-Strategie
Die Hauptidee hinter der OnPush-Strategie manifestiert sich in der Erkenntnis, dass wir, wenn wir Referenztypen als unveränderliche Objekte behandeln, viel schneller erkennen können, ob sich ein Wert geändert hat. Wenn ein Referenztyp unveränderlich ist, bedeutet dies, dass sich die Referenz im Stapelspeicher bei jeder Aktualisierung ändern muss. Jetzt können wir einfach prüfen: Hat sich die Referenz (im Stack) des Referenztyps geändert? Wenn ja, erst dann alle Werte (auf dem Heap) prüfen. Sehen Sie sich die vorherigen Stack-Heap-Diagramme an, wenn dies verwirrend ist.
Die OnPush-Strategie stellt grundsätzlich zwei Fragen statt einer. Hat sich die Referenz des Referenztyps geändert? Wenn ja, haben sich dann die Werte im Heap-Speicher geändert?
Angenommen, wir haben ein unveränderliches Array mit 30 Elementen und möchten wissen, ob es Änderungen gibt. Wir wissen, dass sich die Referenz (auf dem Stapel) geändert haben müsste, damit das unveränderliche Array aktualisiert werden kann. Das bedeutet, dass wir zunächst prüfen können, ob der Verweis auf das Array anders ist, was uns möglicherweise 30 weitere Prüfungen (im Heap) ersparen würde, um festzustellen, welches Element anders ist. Dies wird als OnPush-Strategie bezeichnet.
Sie fragen sich vielleicht, was es bedeutet, Referenztypen als unveränderlich zu behandeln? Das bedeutet, dass wir niemals die Eigenschaft eines Referenztyps festlegen, sondern den Wert insgesamt neu zuweisen. Siehe unten:
Objekte als änderbar behandeln:
static mutable() { var before = {foo: "bar"}; var current = before; current.foo = "hello"; console.log(before === current); // => true }
Objekte als unveränderlich behandeln:
static mutable() { var before = {foo: "bar"}; var current = before; current = {foo "hello"}; console.log(before === current); // => false }
Beachten Sie, dass wir in den obigen Beispielen Referenztypen per Konvention als unveränderlich „behandeln“, sodass wir am Ende immer noch mit veränderlichen Objekten arbeiten, aber nur „vorgeben“, dass sie unveränderlich sind.
Wie implementiert man also die OnPush-Strategie für eine Komponente? Alles, was Sie tun müssen, ist, den Parameter changeDetection
in der Annotation @Component hinzuzufügen.
import {ChangeDetectionStrategy, Component} from '@angular/core'; @Component({ // ... changeDetection: ChangeDetectionStrategy.OnPush }) export class OnPushComponent { // ... }
Unveränderlich.js
Es ist eine gute Idee, Unveränderlichkeit zu erzwingen, wenn man sich entscheidet, die OnPush-Strategie auf eine Angular-Komponente anzuwenden. Hier kommt Immutable.js ins Spiel.
Immutable.js ist eine Bibliothek, die von Facebook für die Unveränderlichkeit in JavaScript erstellt wurde. Sie haben viele unveränderliche Datenstrukturen wie List, Map und Stack. Für die Zwecke dieses Artikels werden Liste und Karte veranschaulicht. Weitere Informationen finden Sie in der offiziellen Dokumentation hier.
Um Immutable.js zu Ihren Projekten hinzuzufügen, stellen Sie bitte sicher, dass Sie in Ihr Terminal gehen und Folgendes ausführen:
$ npm install immutable --save
Stellen Sie außerdem sicher, dass Sie die Datenstrukturen, die Sie verwenden, aus Immutable.js in die Komponente importieren, in der Sie sie verwenden.
import {Map, List} from 'immutable';
So kann eine Immutable.js Map verwendet werden:
var foobar = {foo: "bar"}; var immutableFoobar = Map(foobar); console.log(immutableFooter.get("foo")); // => bar
Und ein Array kann verwendet werden:
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!
Nachteile der Verwendung von Immutable.js
Es gibt ein paar strittige Hauptnachteile bei der Verwendung von Immutable.js.
Wie Sie vielleicht bemerkt haben, ist die Verwendung der API etwas umständlich, und ein traditioneller JavaScript-Entwickler mag dies möglicherweise nicht. Ein ernsteres Problem hat damit zu tun, dass Sie keine Schnittstellen für Ihr Datenmodell implementieren können, da Immutable.js keine Schnittstellen unterstützt.
Einpacken
Sie fragen sich vielleicht, warum die OnPush-Strategie nicht die Standardstrategie für Angular ist. Ich vermute, das liegt daran, dass Angular JavaScript-Entwickler nicht zwingen wollte, mit unveränderlichen Objekten zu arbeiten. Das heißt aber nicht, dass es Ihnen verboten ist, es zu verwenden.
Wenn Sie dies in Ihrem nächsten Webprojekt nutzen möchten, wissen Sie jetzt, wie einfach Angular den Wechsel zu einer anderen Änderungserkennungsstrategie macht.