Der Diff-Algorithmus in React wird auch als Abstimmungsalgorithmus bezeichnet. Die zugehörige Funktion heißt Ich habe schon einige Artikel gelesen, in denen der Diff-Algorithmus als Vergleich zweier Fiber-Bäume beschrieben wird. Das ist falsch. Der eigentliche Diff-Prozess ist ein Vergleich einer Reihe vorhandener Fiber-Knoten und eines neuen, von Node Diff ist in zwei Typen unterteilt:
Die folgende React-Version ist 17.0.1 und die Codedatei ist ReactChildFiber.old.js. Einzelner Knoten DiffSingle-Node-Diff ist relativ einfach. Es wird nur versucht, den Knoten wiederzuverwenden, wenn Schlüssel und Typ gleich sind, andernfalls wird ein neuer Knoten zurückgegeben. In den meisten Fällen weisen wir einzelnen Knoten keine Schlüssel zu, daher ist der Standardwert null, was dasselbe ist. Einzelelement abgleichen// Einzelknotenvergleichsfunktion reconcileSingleElement( returnFiber: Faser, currentFirstChild: Fiber | null, Element: ReactElement, Fahrspuren: Fahrspuren, ): Faser { // Der Schlüssel des neuen reactElement const Schlüssel = Element.Schlüssel; //Aktueller untergeordneter Faserknoten let child = currentFirstChild; während (Kind !== null) { // Nur unterscheiden, wenn der Schlüssel derselbe ist wenn (Kind.Schlüssel === Schlüssel) { Schalter (untergeordnetes Tag) { // ... Standard: { // Wenn die aktuelle Faser und das ReactElement den gleichen Typ haben, dann wenn (child.elementType === element.type) { // Andere Knoten auf derselben Ebene löschen deleteRemainingChildren(returnFiber, child.sibling); // Aktuelle untergeordnete Faser erneut verwenden const vorhanden = useFiber(Kind, Element.Eigenschaften); bestehend.ref = coerceRef(returnFiber, Kind, Element); bestehend.return = returnFiber; // Eine wiederverwendbare untergeordnete Faser zurückgeben bestehende zurückgeben; } brechen; } } // Nicht übereinstimmende Löschknoten deleteRemainingChildren(returnFiber, child); brechen; } anders { //Knoten direkt löschen, wenn der Schlüssel unterschiedlich ist deleteChild(returnFiber, child); } Kind = Kind.Geschwister; } // Neuer Fiber-Knoten const erstellt = createFiberFromElement(element, returnFiber.mode, lanes); erstellt.ref = coerceRef(returnFiber, aktuellesErstesKind, Element); erstellt.return = returnFiber; Rückgabe erstellt; } Multi-Node-DiffDer Quellcode unterteilt mehrere Knoten in Array-Knoten und iterierbare Knoten. wenn (istArray(neuesKind)) { returniere versöhnlichesKinderArray( Rückkehrfaser, aktuellesErstesKind, neues Kind, Fahrspuren, ); } wenn (getIteratorFn(neuesKind)) { returniere reconcileChildrenIterator( Rückkehrfaser, aktuellesErstesKind, neues Kind, Fahrspuren, ); } Die entsprechenden Diff-Funktionen sind Dieser Codeabschnitt ist relativ lang, aber die Logik ist sehr klar und er ist ab der Trennlinie in zwei Durchlaufrunden unterteilt.
Die erste Durchlaufrunde gilt nur für den Fall, dass Schlüssel und Reihenfolge gleich sind. Die Positionen der Knoten, die diesen Schlüsseln entsprechen, haben sich nicht geändert, und es ist nur ein Aktualisierungsvorgang erforderlich. Sobald beim Durchlauf ein anderer Schlüssel gefunden wird, muss aus der Schleife ausgestiegen werden. // Alter Knoten <li key="0"/> <li-Taste="1"/> <li-Taste="2"/> // Neuer Knoten <li key="0"/> <li-Taste="1"/> <li-Taste="5"/> // key="5" ist anders, springe aus der Durchquerung // Die erste Runde der Durchquerung Knoten <li key="0"/> <li-Taste="1"/> // <li key="2"/> und <li key="5"/> verbleiben in der zweiten Runde der Durchquerung und des Vergleichs. Nach der ersten Durchquerungsrunde gibt es zwei Situationen.
Die zweite Durchlaufrunde erfolgt für unterschiedliche Schlüssel oder unterschiedliche Reihenfolgen. Die möglichen Situationen sind wie folgt: // Alter Knoten <li key="0"/> <li-Taste="1"/> <li-Taste="2"/> // Neuer Knoten <li key="0"/> <li-Taste="2"/> <li-Taste="1"/> // Die zweite Durchlaufrunde vergleicht die beiden Knoten <li key="2"/> und <li key="1"/> Die zweite Durchquerungsrunde wird etwas komplizierter sein und später ausführlicher besprochen. Der detaillierte Code lautet wie folgt. KinderArray abgleichenFunktion reconcileChildrenArray( returnFiber: Faser, currentFirstChild: Fiber | null, neueUntergeordnete: Array<*>, Fahrspuren: Fahrspuren, ): Faser | null { // Von der Funktion let resultingFirstChild zurückgegebener Fiber-Knoten: Fiber | null = null; let previousNewFiber: Fiber | null = null; // oldFiber ist eine verknüpfte Liste let oldFiber = currentFirstChild; // Wird verwendet, um zu bestimmen, ob der Knoten verschoben wird. let lastPlacedIndex = 0; sei newIdx = 0; let nextOldFiber = null; // In der ersten Durchlaufrunde werden nur Knoten mit dem gleichen Schlüssel durchlaufen for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) { wenn (alterFiber.index > neueIdx) { nächsteAlteFiber = alteFiber; alteFiber = null; } anders { // Jedes Mal, wenn der alte Fiber-Knoten durchlaufen wird, zeigt er auf das Geschwisterelement, das der Fiber-Knoten der nächsten Schleife ist. nextOldFiber = oldFiber.sibling; } // Wenn der Schlüssel derselbe ist, gib den Faserknoten zurück; wenn der Schlüssel unterschiedlich ist, gib null zurück // Wenn der Typ derselbe ist, den Knoten erneut verwenden, andernfalls einen neuen Knoten zurückgeben const newFiber = updateSlot( Rückkehrfaser, alteFaser, neueKinder[neueIdx], Fahrspuren, ); // newFiber ist null, was darauf hinweist, dass der Schlüssel unterschiedlich ist. Die Schleife wird beendet, wenn (newFiber === null) { wenn (alteFaser === null) { alteFaser = nächstealteFaser; } brechen; } // Wenn newFiber.alternate null ist, handelt es sich um einen neuen Knoten, was darauf hinweist, dass ein neuer Fiber-Knoten mit einem anderen Typ erstellt wird, wenn (oldFiber && newFiber.alternate === null) { // Das oldFiber-Tag muss gelöscht werden deleteChild(returnFiber, oldFiber); } // Platziere den Knoten und aktualisiere den lastPlacedIndex letzterPlatzierterIndex = OrtKind(neueFaser, letzterPlatzierterIndex, neueIdx); // Bilde eine neue Fiber-Knotenkette if (previousNewFiber === null) { resultierendesErstesKind = neueFaser; } anders { vorherigeNeueFiber.geschwister = neueFiber; } vorherigeNeueFiber = neueFiber; alteFaser = nächstealteFaser; } /* Nach der ersten Durchlaufrunde ist die Anzahl der neuen Knoten geringer als die Anzahl der alten Knoten. newChildren wurde durchlaufen und die verbleibenden Faserknoten werden gelöscht. Die mögliche Situation ist wie folgt ⬇️ Vorher <li key="0"/> <li-Taste="1"/> <li-Taste="2"/> Neu <li key="0"/> <li-Taste="1"/> <li key="2"/> wird gelöscht*/ wenn (newIdx === neueKinder.Länge) { löscheRestKinder(returnFiber, oldFiber); gib das resultierendeErsteKind zurück; } /* Nach der ersten Durchlaufrunde ist die Anzahl der neuen Knoten größer als die Anzahl der alten Knoten. OldFiber wurde durchlaufen. Die mögliche Situation ist wie folgt ⬇️ Vorher <li key="0"/> <li-Taste="1"/> Neu <li key="0"/> <li-Taste="1"/> <li-Taste="2"/> Ein neuer <li key="2"/> wird hinzugefügt. Dieser Abschnitt ist die Einfügelogik des neuen Knotens*/ wenn (alteFaser === null) { für (; newIdx < newChildren.length; newIdx++) { const newFiber = createChild(returnFiber, newChildren[newIdx], lanes); wenn (newFiber === null) { weitermachen; } letzterPlatzierterIndex = OrtKind(neueFaser, letzterPlatzierterIndex, neueIdx); // Bilde eine neue Fiber-Knotenkette if (previousNewFiber === null) { resultierendesErstesKind = neueFaser; } anders { vorherigeNeueFiber.geschwister = neueFiber; } vorherigeNeueFiber = neueFiber; } gib das resultierendeErsteKind zurück; } // --------------------------------------------------------------------- // Verwende die verbleibende oldFiber, um eine Schlüssel->Fiber-Knoten-Map zu erstellen, sodass der entsprechende alte Fiber-Knoten über den Schlüssel const existingChildren = mapRemainingChildren(returnFiber, oldFiber); abgerufen werden kann. // Zweite Durchlaufrunde, fahren Sie mit dem Durchlaufen der verbleibenden Knoten fort. Diese Knoten müssen möglicherweise verschoben oder gelöscht werden für (; newIdx < newChildren.length; newIdx++) { // Den alten Knoten entsprechend dem Schlüssel aus der Karte abrufen und den aktualisierten neuen Knoten zurückgeben const newFiber = updateFromMap( bestehendeKinder, Rückkehrfaser, neueIdx, neueKinder[neueIdx], Fahrspuren, ); wenn (neueFiber !== null) { // Den neuen Knoten wiederverwenden und den alten Knoten aus der Karte löschen. Die entsprechende Situation kann eine Positionsänderung sein, wenn (newFiber.alternate !== null) { // Die wiederverwendeten Knoten müssen aus der Karte entfernt werden, da die verbleibenden Knoten in der Karte als Löschung markiert werden existingChildren.delete( newFiber.key === null ? ); } // Platzieren Sie den Knoten und bestimmen Sie, ob er verschoben werden soll lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); wenn (vorherigeNeueFiber === null) { resultierendesErstesKind = neueFaser; } anders { vorherigeNeueFiber.geschwister = neueFiber; } vorherigeNeueFiber = neueFiber; } } // Löschen Sie die verbleibenden nutzlosen Knoten existingChildren.forEach(child => deleteChild(returnFiber, child)); gib das resultierendeErsteKind zurück; } Die erste Durchquerungsrunde ist relativ einfach zu verstehen. Hier werden wir die zweite Durchquerungsrunde im Detail analysieren, da in der zweiten Runde die Frage aufgeworfen wird, ob die Wiederverwendung verschoben werden muss. In der zweiten Durchlaufrunde werden zunächst die verbleibenden Die folgende Durchquerung verwendet immer noch den neuen Knoten als Durchquerungsobjekt. Bei jeder Durchquerung wird der Schlüssel des neuen Knotens verwendet, um den alten Knoten aus der Map zu entfernen und zu vergleichen, ob er wiederverwendet werden kann. Die entsprechende Funktion ist Wenn der Knoten ein Wie kann festgestellt werden, ob ein Knoten verschoben wurde? Hier gibt es eine Variable Wenn Funktion OrtKind( newFiber: Faser, lastPlacedIndex: Nummer, newIndex: Nummer, ): Nummer { newFiber.index = neuerIndex; const current = neueFiber.alternate; wenn (aktuell !== null) { const alterIndex = aktueller.index; if (alterIndex < letzterplatzierterIndex) { // Knotenbewegung newFiber.flags = Platzierung; gibt letzten platzierten Index zurück; } anders { // Die Knotenposition hat sich nicht geändert return oldIndex; } } anders { // Neuen Knoten einfügen newFiber.flags = Placement; gibt letzten platzierten Index zurück; } } Zum Beispiel: // altes abcd // Neues acbd abcd sind alles Schlüsselwerte. Nach der ersten Durchlaufrunde müssen die verbleibenden Knoten verglichen werden: // Alter BCD // Neues CBD Knoten a wurde in der ersten Runde wiederverwendet und placeChild wurde aufgerufen. Zu diesem Zeitpunkt ist der lastPlacedIndex-Wert 0. Beim Eintritt in die zweite Durchlaufrunde ist der neue Knoten immer noch das Durchlaufobjekt. c => existiert im alten Knoten und kann wiederverwendet werden. Sein Index im alten Knoten ist 2. 2 > lastPlacedIndex(0). Kein Verschieben nötig. Weisen Sie lastPlacedIndex den Wert 2 zu. b => existiert im alten Knoten und kann wiederverwendet werden. Sein Index im alten Knoten ist 1, 1 < lastPlacedIndex(2). Es muss verschoben und mit Placement markiert werden. d => existiert im alten Knoten und kann wiederverwendet werden. Sein Index im alten Knoten ist 3, 3 > lastPlacedIndex(2), also muss er nicht verschoben werden. Anhand dieses Beispiels können wir erkennen, dass in React die Knoten auf der rechten Seite, die nicht verschoben werden müssen, als Referenzen verwendet werden und die Knoten, die verschoben werden müssen, alle gleichmäßig von links nach rechts verschoben werden. Im darauffolgenden Layout-Schritt werden die mit ZusammenfassenDer Kerncode des Diff-Algorithmus in React ist nicht sehr lang, aber die Einführung des Schlüssels ändert die Komplexität geschickt von O(n3) auf O(n). Der interne Wettbewerb unter den Programmierern ist zu groß, also muss ich den Quellcode lernen. Oben finden Sie den detaillierten Inhalt der Quellcodeanalyse des React-Diff-Algorithmus. Weitere Informationen zum React-Diff-Algorithmus finden Sie in den anderen verwandten Artikeln auf 123WORDPRESS.COM! Das könnte Sie auch interessieren:
|
<<: Detaillierte Erklärung zur Verwendung des Alias-Befehls unter Linux
>>: So beheben Sie den Fehler „MySQL-Dienst kann nicht gestartet werden, Fehler 1069“
Inhaltsverzeichnis Abfragehintergrund 1. Like-Abf...
Vorwort: Wie passt sich Vue basierend auf einem a...
JS-Lauftrilogie js-Ausführungscode ist in drei Sc...
Inhaltsverzeichnis 1. Einleitung 2. es5-Methode 3...
In diesem Artikel wird der spezifische JavaScript...
Heute habe ich diese Eingabeaufforderung gefunden...
Die Verwaltung des Speicherplatzes ist für System...
In diesem Artikelbeispiel wird der spezifische Co...
1. Voraussetzungen JDK wurde installiert echo $PA...
Die von Navicat exportierten Daten können nicht i...
1. Vue-Projekt verpacken Geben Sie den folgenden ...
Systemumgebung: Windows 7 1. Docker installieren ...
Lassen Sie uns Nginx installieren und ausprobiere...
Da für den Import benutzerdefinierter Ubuntu-Imag...
Inhaltsverzeichnis So reduzieren Sie ein Array 1....