Hallo Habr! Ich mache Sie auf eine Übersetzung des Artikels "React Fibre Architecture" von Andrew Clark aufmerksam .
Eintrag
React Fibre ist eine progressive Implementierung des wichtigsten React-Algorithmus. Dies ist der Höhepunkt einer zweijährigen Studie des React-Entwicklungsteams.
Das Ziel von Fibre ist es, die Produktivität bei der Entwicklung von Aufgaben wie Animation, Organisieren von Elementen auf einer Seite und Verschieben von Elementen zu steigern. Das Hauptmerkmal ist das inkrementelle Rendern: Die Möglichkeit, die Rendering-Arbeit in Einheiten zu unterteilen und auf mehrere Frames zu verteilen.
Weitere wichtige Funktionen sind die Möglichkeit, eingehende Aktualisierungen des DOM-Baums anzuhalten, abzubrechen oder wiederzuverwenden, verschiedene Arten von Aktualisierungen zu priorisieren und die Koordination von Grundelementen.
Bevor Sie diesen Artikel lesen, empfehlen wir Ihnen, sich mit den Grundprinzipien von React vertraut zu machen:
Rückblick
Was ist Versöhnung?
Die Abstimmung ist ein Reaktionsalgorithmus, mit dem ein Elementbaum von einem anderen unterschieden wird, um die Teile zu bestimmen, die ersetzt werden müssen.
Ein Update ist eine Änderung der Daten, die zum Rendern einer React-Anwendung verwendet werden. Dies ist normalerweise das Ergebnis des Aufrufs der setState-Methode. Das Endergebnis des Renderns der Komponente.
Die Schlüsselidee der React-API besteht darin, sich Updates so vorzustellen, als könnten sie zu einem vollständigen Rendern der Anwendung führen. Auf diese Weise kann der Entwickler deklarativ handeln und sich keine Gedanken darüber machen, wie rational der Übergang der Anwendung von einem Zustand in einen anderen sein wird (von A nach B, von B nach C, von C nach A usw.).
Im Allgemeinen funktioniert das Rendern der gesamten Anwendung für jede Änderung nur in den meisten herkömmlichen Anwendungen. In der realen Welt wirkt sich dies nachteilig auf die Leistung aus. Der Vorgang umfasst Optimierungen, die eine vollständige Renderansicht erstellen, ohne einen großen Teil der Leistung zu beeinträchtigen. Die meisten dieser Optimierungen umfassen einen Prozess namens Abgleich.
Die Abstimmung ist ein Algorithmus hinter dem, was wir gewohnt sind, "virtuelles DOM" zu nennen. Die Definition klingt ungefähr so: Wenn Sie eine React-Anwendung rendern, wird der Elementbaum, der die Anwendung beschreibt, im reservierten Speicher generiert. Dieser Baum wird dann in die Rendering-Umgebung aufgenommen. Am Beispiel einer Browser-Anwendung wird er in eine Reihe von DOM-Operationen übersetzt. Wenn der Anwendungsstatus aktualisiert wird (normalerweise durch Aufrufen von setState), wird ein neuer Baum generiert. Der neue Baum wird mit dem vorherigen Baum verglichen, um genau die Vorgänge zu berechnen und zu aktivieren, die zum erneuten Zeichnen der aktualisierten Anwendung erforderlich sind.
Obwohl Fibre eine enge Implementierung des Reconcilers ist, wird der in der React-Dokumentation erläuterte High-Level-Algorithmus ziemlich gleich sein.
Schlüsselkonzepte:
- Verschiedene Arten von Komponenten legen die Erzeugung wesentlich unterschiedlicher Bäume nahe. React versucht nicht, sie zu vergleichen, sondern ersetzt einfach den alten Baum vollständig.
- Listen werden anhand von Schlüsseln unterschieden. Schlüssel sollten "dauerhaft, vorhersehbar und eindeutig" sein.
Versöhnung vs. Rendern
Der DOM-Baum ist eine der Umgebungen, die React zeichnen kann, der Rest kann nativen iOS- und Android-Ansichten mit React Native zugeordnet werden (aus diesem Grund ist Virtual Dom ein wenig unangemessener Name).
Der Grund, warum React so viele Ziele unterstützt, liegt darin, dass React so aufgebaut ist, dass Abgleich und Rendern separate Phasen sind. Der arbeitende Abgleich berechnet, welche Teile des Baums geändert wurden. Der Renderer verwendet diese Informationen später, um den zuvor gerenderten Baum zu aktualisieren.
Diese Trennung bedeutet, dass React DOM und React Native ihre eigenen Rendering-Mechanismen verwenden können, wenn dasselbe Caching-Tool verwendet wird, das sich in React Core befindet.
Fiber ist eine neu gestaltete Implementierung des Abstimmungsalgorithmus. Es hat eine indirekte Beziehung zum Rendern, während die Rendermechanismen (Rendering) geändert werden können, um alle Vorteile der neuen Architektur zu unterstützen.
Planung ist ein Prozess, der festlegt, wann die Arbeit abgeschlossen sein soll.
Arbeit - alle Berechnungen, die durchgeführt werden müssen. Arbeit ist normalerweise das Ergebnis eines Updates (z. B. Aufruf von setState).
Die Prinzipien der React-Architektur sind so gut, dass sie nur mit diesem Zitat beschrieben werden können:
In der aktuellen React-Implementierung wird der Baum rekursiv durchlaufen und die Rendering-Funktionen für den gesamten aktualisierten Baum in einem einzigen Tick (16 ms) aufgerufen. In Zukunft kann er jedoch einige Aktualisierungen abbrechen, um Frame-Sprünge zu verhindern.
Dies ist ein häufig diskutiertes Thema in Bezug auf React Design. Einige beliebte Bibliotheken implementieren einen Push-Ansatz, bei dem Berechnungen durchgeführt werden, wenn neue Daten verfügbar sind. React hält sich jedoch an den Pull-Ansatz, bei dem Berechnungen bei Bedarf abgebrochen werden können.
React ist keine Bibliothek zur Verarbeitung verallgemeinerter Daten. Dies ist eine Bibliothek zum Erstellen von Benutzeroberflächen. Wir sind der Meinung, dass es eine eindeutige Position in der Anwendung haben sollte, um festzustellen, welche Berechnungen geeignet sind und welche nicht.
Wenn sich etwas hinter den Kulissen befindet, können wir die gesamte damit verbundene Logik rückgängig machen. Wenn die Daten schneller als die Frame-Rendering-Rate ankommen, können wir die Aktualisierungen kombinieren. Wir können die Priorität der Arbeit, die sich aus der Benutzerinteraktion ergibt (z. B. das Erscheinen einer Animation beim Drücken einer Schaltfläche), gegenüber weniger wichtigen Arbeiten im Hintergrund (Rendern neuer vom Server geladener Inhalte) erhöhen, um das Herunterladen von Frames zu verhindern.
Schlüsselkonzepte:
- In Benutzeroberflächen ist es nicht wichtig, dass jedes Update sofort angewendet wird. In der Tat wird dieses Verhalten überflüssig sein, es wird zum Fall von Frames und zur Verschlechterung von UX beitragen.
- Verschiedene Arten von Updates haben unterschiedliche Prioritäten - Animationsupdates sollten schneller enden als beispielsweise das Aktualisieren des Datenspeichers.
- Bei einem Push-basierten Ansatz muss die Anwendung (Sie als Entwickler) entscheiden, wie die Arbeit geplant werden soll. Ein Pull-basierter Ansatz ermöglicht es dem Framework, Entscheidungen für Sie zu treffen.
Reagieren im Moment hat nicht den Vorteil, in erheblichem Maße zu planen; Aktualisierungsergebnisse für den gesamten Teilbaum werden sofort gezeichnet. Die Schlüsselidee von Fibre besteht darin, die Elemente im React-Kernel-Algorithmus sorgfältig auszuwählen, um die Zeitplanung anzuwenden.
Was ist Faser?
Wir werden das Herz der React Fibre-Architektur diskutieren. Fiber ist eine Abstraktion auf niedrigerer Ebene über die Anwendung, als Entwickler es gewohnt sind zu denken. Wenn Sie Ihre Versuche, es zu verstehen, für hoffnungslos halten, lassen Sie sich nicht entmutigen (Sie sind nicht allein). Schau weiter und es wird endlich Früchte tragen.
Und so!
Wir haben dieses Hauptziel der Fiber-Architektur erreicht - React die Planung nutzen zu lassen. Insbesondere müssen wir in der Lage sein:
- Stoppen Sie die Arbeit und kehren Sie später zurück.
- verschiedene Arten von Arbeit priorisieren.
- Verwenden Sie die zuvor geleistete Arbeit erneut.
- brechen Sie die Arbeit ab, wenn sie nicht mehr benötigt wird.
Um all dies zu tun, müssen wir zuerst die Arbeit in Einheiten aufteilen. In gewissem Sinne ist dies Faser. Faser repräsentiert eine Arbeitseinheit.
Um weiter zu gehen, kehren wir zum Grundkonzept von React "Komponenten als Funktionsdaten" zurück , das häufig ausgedrückt wird als:
v = f(d)
Daraus folgt, dass das Rendern einer React-Anwendung dem Aufrufen einer Funktion gleicht, deren Hauptteil Aufrufe anderer Funktionen enthält, und so weiter. Diese Analogie ist nützlich, wenn man an Fasern denkt.
Die Art und Weise, wie Computer die Ausführungsreihenfolge eines Programms überprüfen, wird als Aufrufstapel bezeichnet. Wenn die Funktion abgeschlossen ist, wird der neue Stapelcontainer zum Stapel hinzugefügt. Dieser Stapelcontainer repräsentiert die Arbeit einer Funktion.
Wenn Sie mit Benutzeroberflächen arbeiten, wird sofort zu viel Arbeit geleistet. Dies ist ein Problem. Dies kann zu Sprüngen in der Animation führen und wird zeitweise angezeigt. Darüber hinaus sind einige dieser Arbeiten möglicherweise nicht erforderlich, wenn sie durch das neueste Update ersetzt werden. Zu diesem Zeitpunkt unterscheidet sich der Vergleich zwischen der Benutzeroberfläche und der Funktion, da Komponenten eine spezifischere Verantwortung haben als Funktionen im Allgemeinen.
Die neuesten Browser und React Native implementieren APIs, mit denen dieses Problem gelöst werden kann:
requestIdleCallback verteilt Aufgaben so, dass Funktionen mit niedriger Priorität in einem einfachen Zeitraum aufgerufen werden, und requestAnimationFrame verteilt Aufgaben so, dass Funktionen mit hoher Priorität im nächsten Frame aufgerufen werden. Das Problem ist, dass Sie zur Verwendung dieser APIs die Rendering-Arbeit in inkrementelle Einheiten aufteilen müssen. Wenn Sie sich nur auf den Aufrufstapel verlassen, wird die Arbeit fortgesetzt, bis der Stapel leer ist.
Wäre es nicht schön, wenn wir das Verhalten des Aufrufstapels anpassen könnten, um die Anzeige von Teilen der Benutzeroberfläche zu optimieren? Wäre es schön, wenn wir den Aufrufstapel brechen könnten, um Container manuell zu manipulieren?
Dies ist die Berufung von React Fibre. Fibre ist eine neue Stack-Implementierung, die auf React-Komponenten zugeschnitten ist. Sie können sich eine einzelne Glasfaser als virtuellen Stapelcontainer vorstellen.
Der Vorteil dieser Implementierung des Stapels besteht darin, dass Sie den Containerstapel im Speicher speichern und dann (und wo) ausführen können. Dies ist eine entscheidende Definition für das Erreichen Ihrer Planungsziele.
Neben der Planung zeigen manuelle Aktionen mit dem Stack das Potenzial von Konzepten wie Konsistenz (Parallelität) und Fehlerbehandlung (Fehlergrenzen).
Im nächsten Abschnitt betrachten wir die Struktur der Fasern.
Faserstruktur
Insbesondere ist eine „Faser“ ein JavaScript-Objekt, das Informationen zu einer Komponente, ihrer Eingabe und Ausgabe enthält.
Die Faser stimmt mit dem Stapelbehälter überein, aber sie stimmt auch mit dem Wesen der Komponente überein.
Hier sind einige wichtige Eigenschaften der „Faser“ (Diese Liste ist nicht vollständig):
Typ und Schlüssel
Typ und Schlüssel dienen sowohl der Faser als auch den React-Elementen. Wenn eine Faser erstellt wird, werden diese beiden Felder direkt darauf kopiert.
Der Fasertyp beschreibt die Komponente, der er entspricht. Für die Zusammensetzung von Komponenten ist Typ eine Funktion oder Klasse von Komponenten. Für Servicekomponenten (div, span) ist der Typ eine Zeichenfolge.
Konzeptionell ist ein Typ eine Funktion, deren Ausführung von einem Stapelcontainer verfolgt wird.
Zusammen mit dem Typ wird der Schlüssel beim Vergleichen von Bäumen verwendet, um festzustellen, ob die Faser wiederverwendet werden kann.
Kind und Geschwister
Diese Felder zeigen auf andere Fasern und beschreiben die rekursive Struktur der Fasern.
Das untergeordnete Glasfaserkabel entspricht dem Wert, der beim Aufrufen der Rendermethode für die Komponente zurückgegeben wurde. Im folgenden Beispiel:
function Parent() { return <Child /> }
Parent Fibre Child entspricht Child.
Das relative (oder Nachbar-) Feld wird verwendet, wenn beim Rendern mehrere untergeordnete Elemente zurückgegeben werden (eine neue Funktion in Fibre):
function Parent() { return [<Child1 />, <Child2 />] }
Kinderfasern sind eine einfach verknüpfte Liste, an deren Spitze das erste Kind steht. In diesem Beispiel ist das untergeordnete Element Child1, und die Verwandten von Child1 sind Child2.
Zurück zu unserer Analogie mit Funktionen: Sie können sich eine untergeordnete Faser als eine Funktion vorstellen, die am Ende aufgerufen wird (Schwanzfunktion).
Wikipedia-Beispiel:
function foo(data) { a(data); return b(data); }
In diesem Beispiel lautet die Schwanzfunktion b.
Rückgabewert (Rückgabe)
Rückfaser ist die Faser, zu der das Programm nach der Verarbeitung der aktuellen Faser zurückkehren soll. Dies entspricht der Rückgabe der Adresse des Stapelcontainers.
Es kann auch als Ausgangsfaser betrachtet werden.
Wenn eine Faser mehrere untergeordnete Fasern hat, gibt die Rückgabe jeder untergeordneten Faser die übergeordnete Faser zurück. Im obigen Beispiel ist die Rückgabefaser von Child1 und Child2 Parent.
Aktuelle und zwischengespeicherte Eigenschaften (ausstehendeProps und gespeicherteProps)
Konzeptionell sind Eigenschaften Funktionsargumente. Die aktuellen Fasereigenschaften sind eine Reihe dieser Eigenschaften zu Beginn der Ausführung, zwischengespeicherte Eigenschaften sind eine Menge am Ende der Ausführung.
Wenn die Eigenschaften der Eingabewartung zwischengespeichert werden, bedeutet dies, dass die vorherige Faserausgabe ohne Berechnungen wiederverwendet werden kann.
Priorität der aktuellen Arbeit (ausstehendeWorkPriority)
Der Umfang der prioritätsbestimmenden Arbeit wird von der Faser angezeigt. Das Prioritätsstufenmodul in React ReactPrioritylevel enthält verschiedene Prioritätsstufen und deren Darstellung.
Beginnend mit einer Ausnahme vom Typ NoWork (0) definiert eine höhere Zahl die niedrigste Priorität. Mit der folgenden Funktion können Sie beispielsweise überprüfen, ob die Faserpriorität größer als die angegebene Stufe ist:
function matchesPriority(fiber, priority) { return fiber.pendingWorkPriority !== 0 && fiber.pendingWorkPriority <= priority }
Diese Funktion dient nur zu Illustrationszwecken. Es ist nicht Teil der React Fibre-Datenbank.
Der Scheduler verwendet das Prioritätsfeld, um die nächste Arbeitseinheit zu finden, die ausgeführt werden kann. Wir werden diesen Algorithmus im nächsten Abschnitt diskutieren.
Alternative (oder Paar)
Faser aktualisieren (spülen) - Dies bedeutet, dass die Ausgabe auf dem Bildschirm angezeigt wird.
Faser in Entwicklung (in Arbeit) - Faser, die noch nicht gebaut wurde; Mit anderen Worten, es handelt sich um einen Stapelcontainer, der noch nicht zurückgegeben wurde.
Zu jedem Zeitpunkt hat die Essenz der Komponente nicht mehr als zwei Zustände für die Faser, die entsprechen: Faser in ihrem aktuellen Zustand, aktualisierte Faser oder Faser in Entwicklung.
Auf die aktuelle Faser folgt die zu entwickelnde Faser, und dann wird die Faser wiederum aktualisiert.
Der nächste Faserzustand wird träge mit der Funktion cloneFiber erstellt. Fast immer versucht cloneFiber beim Erstellen eines neuen Objekts, eine Alternative (ein Paar) von Glasfasern wiederzuverwenden, falls vorhanden, und minimiert gleichzeitig die Kosten für Ressourcen.
Sie sollten sich das Dampffeld (oder eine Alternative) als Implementierungsdetail vorstellen, aber es taucht so oft in der Dokumentation auf, dass es einfach unmöglich war, es nicht zu erwähnen.
Schlussfolgerung ist ein Serviceelement (oder eine Reihe von Serviceelementen); Blattknoten Reagieren Sie auf Anwendungen. Sie sind für jede Anzeigeumgebung spezifisch (in einem Browser ist dies beispielsweise "div", "span" usw.). In JSX werden sie als Tag-Namen in Kleinbuchstaben bezeichnet.
Fazit: Ich empfehle, die Funktionen der neuen React v16.0-Architektur auszuprobieren