Wie kann man die Essenz des virtuellen DOM verstehen und beherrschen? Ich empfehle jedem, das Snabbdom-Projekt zu lernen.
Snabbdom ist eine virtuelle DOM-Implementierungsbibliothek. Erstens ist der Code relativ klein und der Kerncode umfasst nur wenige hundert Zeilen. Drittens greift Vue auf die Ideen dieses Projekts zurück. Die Design-/Implementierungs- und Erweiterungsideen dieses Projekts sind Ihre Referenz wert.
snabb /snab/, Schwedisch, bedeutet schnell.
Passen Sie Ihre bequeme Sitzhaltung an und machen Sie sich auf den Weg. Um virtuelles DOM zu lernen, müssen wir zunächst die Grundkenntnisse von DOM und die Schwachstellen bei der direkten Bedienung von DOM kennen.
DOM (Document Object Model) ist ein Dokumentobjektmodell, das eine Objektbaumstruktur verwendet, um ein HTML/XML-Dokument darzustellen. Das Ende jedes Zweigs des Baums ist ein Knoten. Mit den Methoden der DOM-API können Sie diesen Baum auf bestimmte Weise bearbeiten. Mit diesen Methoden können Sie die Struktur, den Stil oder den Inhalt des Dokuments ändern.
Alle Knoten im DOM-Baum sind zunächst Node
Node
ist eine Basisklasse. Element
, Text
und Comment
erben alle davon.
Mit anderen Worten: Element
, Text
und Comment
sind drei spezielle Node
, die jeweils ELEMENT_NODE
genannt werden.
TEXT_NODE
und COMMENT_NODE
repräsentieren Elementknoten (HTML-Tags), Textknoten und Kommentarknoten. Element
hat auch eine Unterklasse namens HTMLElement
. Was ist der Unterschied zwischen HTMLElement
und Element
? HTMLElement
repräsentiert Elemente in HTML, wie zum Beispiel: <span>
, <img>
usw., und einige Elemente entsprechen nicht dem HTML-Standard, wie zum Beispiel <svg>
. Sie können die folgende Methode verwenden, um festzustellen, ob es sich bei diesem Element um HTMLElement
handelt:
document.getElementById('myIMG') exampleof HTMLElement;
Für den Browser ist es „teuer“, das DOM zu erstellen. Nehmen wir ein klassisches Beispiel. Wir können über document.createElement('p')
ein einfaches p-Element erstellen und alle Attribute ausdrucken:
Sie können sehen, dass es bei der häufigen Aktualisierung komplexer DOM-Bäume zu Leistungsproblemen kommt. Virtual DOM verwendet ein natives JS-Objekt, um einen DOM-Knoten zu beschreiben, sodass die Erstellung eines JS-Objekts viel kostengünstiger ist als die Erstellung eines DOM-Objekts.
VNode ist eine Objektstruktur, die das virtuelle DOM in Snabbdom beschreibt. Der Inhalt lautet wie folgt:
Typ Key = String-Nummer |. Schnittstelle VNode { // CSS-Selektor, zum Beispiel: 'p#container'. sel: string |. undefiniert; // CSS-Klassen, Attribute usw. über Module manipulieren. Daten: VNodeData |. undefiniert; // Virtuelles untergeordnetes Knotenarray, Array-Elemente können auch Zeichenfolgen sein. Kinder: Array<VNode |. string> |. // Zeigen Sie auf das tatsächlich erstellte DOM-Objekt. ulme: Knoten |. undefiniert; /** * Für das Textattribut gibt es zwei Situationen: * 1. Der Sel-Selektor ist nicht festgelegt, was darauf hinweist, dass der Knoten selbst ein Textknoten ist. * 2. sel ist gesetzt, was anzeigt, dass der Inhalt dieses Knotens ein Textknoten ist. */ Text: Zeichenfolge |. undefiniert; // Wird verwendet, um eine Kennung für das vorhandene DOM bereitzustellen, die unter den Geschwisterelementen eindeutig sein muss, um unnötige Rekonstruktionsvorgänge effektiv zu vermeiden. Schlüssel: Schlüssel |. undefiniert; } // Einige Einstellungen für vnode.data, Klassen- oder Lebenszyklus-Funktions-Hooks usw. Schnittstelle VNodeData { Requisiten?: Requisiten; attrs?: Attrs; Klasse?: Klassen; Stil?: VNodeStyle; Datensatz?: Datensatz; ein?: Ein; attachmentData?: AttachData; Haken?: Haken; Schlüssel?: Schlüssel; ns?: string; // für SVGs fn?: () => VNode; // für Thunks args?: any[]; // für Thunks is?: string; // für benutzerdefinierte Elemente v1 [key: string]: any; // für jedes andere Modul eines Drittanbieters }
Definieren Sie beispielsweise ein Vnode-Objekt wie folgt:
const vnode = h( 'p#container', { Klasse: { aktiv: wahr } }, [ h('span', { style: { fontWeight: 'bold' } }, 'Das ist fett'), „Und das ist nur normaler Text“ ]);
Wir erstellen Vnode-Objekte über die Funktion h(sel, b, c)
. Die Implementierung des h()
-Codes bestimmt hauptsächlich, ob die Parameter b und c vorhanden sind, und verarbeitet sie in Daten und untergeordnete Elemente, die schließlich die Form eines Arrays haben. Schließlich wird das oben definierte VNode
Typformat über die Funktion vnode()
zurückgegeben.
Nehmen wir zunächst ein einfaches Beispieldiagramm des laufenden Prozesses und erstellen zunächst ein allgemeines Prozesskonzept:
Bei der Differenzverarbeitung handelt es sich um den Prozess zur Berechnung der Differenz zwischen neuen und alten Knoten.
Schauen wir uns einen Beispielcode an, der von Snabbdom ausgeführt wird:
import { init, Klassenmodul, propsModule, styleModule, eventListenersModule, H, } von 'snabbdom'; const patch = init([ // Initialisieren Sie die Patch-Funktion classModule, indem Sie das Modul übergeben. // Aktivieren Sie die Klassenfunktion propsModule. // Unterstützen Sie die Übergabe von props styleModule, // Unterstützt Inline-Stile und Animationen eventListenersModule, // Fügt Event-Listening hinzu]); // <p id="container"></p> const container = document.getElementById('container'); const vnode = h( 'p#container.two.classes', { on: { click: someFn } }, [ h('span', { style: { fontWeight: 'bold' } }, 'Das ist fett'), 'und das ist nur normaler Text', h('a', { props: { href: '/foo' } }, "Ich bringe dich hin!"), ] ); // Einen leeren Elementknoten übergeben. patch(container, vnode); const newVnode = h( 'p#container.two.classes', { on: { click: anotherEventHandler } }, [ H( 'Spanne', { Style: { FontWeight: 'normal', FontStyle: 'italic' } }, „Das ist jetzt Kursivschrift“ ), ' und das ist immer noch nur normaler Text', h('a', { props: { href: ''/bar' } }, "Ich bringe dich hin!"), ] ); // Rufen Sie patch() erneut auf, um den alten Knoten auf den neuen Knoten zu aktualisieren. patch(vnode, newVnode);
Wie aus dem Prozessdiagramm und dem Beispielcode ersichtlich ist, wird der laufende Prozess von Snabbdom wie folgt beschrieben:
Rufen Sie zunächst init()
zur Initialisierung auf, und die zu verwendenden Module müssen während der Initialisierung konfiguriert werden. Beispielsweise wird classModule
zum Konfigurieren des Klassenattributs von Elementen in Form von Objekten verwendet; das Modul eventListenersModule
wird zum Konfigurieren von Ereignis-Listenern usw. verwendet. Die Funktion patch()
wird nach dem Aufruf von init()
zurückgegeben.
Erstellen Sie das initialisierte Vnode-Objekt über die Funktion h()
, rufen Sie die Funktion patch()
auf, um es zu aktualisieren, und erstellen Sie schließlich das echte DOM-Objekt über createElm()
.
Wenn eine Aktualisierung erforderlich ist, erstellen Sie ein neues Vnode-Objekt, rufen Sie patch()
zum Aktualisieren auf und schließen Sie die differenzielle Aktualisierung dieses Knotens und der untergeordneten Knoten über patchVnode()
und updateChildren()
ab.
Snabbdom nutzt Moduldesign, um die Aktualisierung verwandter Eigenschaften zu erweitern, anstatt alles in den Kerncode zu schreiben. Wie wird das also konzipiert und umgesetzt? Kommen wir als Nächstes zunächst zum Kerninhalt von Kangkangs Design, Hooks – Lebenszyklusfunktionen.
Snabbdom bietet eine Reihe umfangreicher Lebenszyklusfunktionen, auch bekannt als Hook-Funktionen. Diese Lebenszyklusfunktionen sind in Modulen anwendbar oder können direkt auf vnode definiert werden. Beispielsweise können wir die Hook-Ausführung auf vnode wie folgt definieren:
h('p.row', { Schlüssel: 'myRow', Haken: { einfügen: (vnode) => { console.log(vnode.elm.offsetHeight); }, }, });
Alle Lebenszyklusfunktionen werden wie folgt deklariert:
Name | Trigger-Knoten | -Callback-Parameter |
---|---|---|
pre | Patch-Startausführung | Keine |
init | Vnode wird hinzugefügt. | vnode |
emptyVnode, vnode | create | DOM |
insert | Element | vnode |
auf | prepatch |
OldVnode
oldVnode, vnode | Vnode | |
update | Postpatch | oldVnode, vnode |
postpatch | wurde gepatcht. | oldVnode, vnode |
, | Vnode | post |
vnode, removeCallback | remove | direkt |
vnode | destroy | entfernt |
zum Modul: pre
, create
, update
, destroy
, remove
, post
. Auf Vnode-Deklarationen anwendbar sind: init
, create
, insert
, prepatch
, update
, postpatch
, destroy
, remove
.
Schauen wir uns an, wie Kangkang implementiert wird. Nehmen wir als Beispiel classModule
Anweisung von Kangkang:
import { VNode, VNodeData } from „../vnode“; import { Module } from "./module"; Exporttyp Classes = Record<string, boolean>; Funktion updateClass(oldVnode: VNode, vnode: VNode): void { // Hier sind die Details zum Aktualisieren des Klassenattributs. Ignorieren Sie es vorerst. // ... } export const classModule: Module = { create: updateClass, update: updateClass };
Sie können sehen, dass die letzte exportierte Moduldefinition ein Objekt ist. Der Schlüssel des Objekts ist der Name der Module
wie folgt:
import { PreHook, CreateHook, UpdateHook, DestroyHook, RemoveHook, PostHook, } from "../hooks"; Exporttyp Modul = Teilweise<{ pre: PreHook; erstellen: CreateHook; Update: UpdateHook; zerstören: DestroyHook; entfernen: RemoveHook; Beitrag: PostHook; }>;
Partial
bedeutet in TS, dass die Attribute jedes Schlüssels im Objekt leer sein können. Das heißt, Sie definieren einfach in der Moduldefinition, um welchen Hook es sich handelt. Nachdem der Hook nun definiert ist, wie wird er im Prozess ausgeführt? Schauen wir uns als Nächstes die Funktion init()
an:
// Welche Hooks können im Modul definiert werden? const Hooks: Array<keyof Module> = [ "erstellen", "aktualisieren", "entfernen", "zerstören", „vor“, "Post", ]; Exportfunktion init( Module: Array<Partial<Module>>, domApi?: DOMAPI, Optionen?: Optionen ) { //Die im Modul definierte Hook-Funktion wird schließlich hier gespeichert. const cbs: ModuleHooks = { erstellen: [], aktualisieren: [], entfernen: [], zerstören: [], vor: [], Post: [], }; // ... // Durchlaufen Sie die im Modul definierten Hooks und speichern Sie sie zusammen. for (const Hook of Hooks) { for (const Modul von Modulen) { const currentHook = module[hook]; if (currentHook !== undefiniert) { (cbs[hook] as any[]).push(currentHook); } } } // ... }
Sie können sehen, dass init()
während der Ausführung zunächst jedes Modul durchläuft und dann die Hook-Funktion im cbs
-Objekt speichert. Bei der Ausführung können Sie patch()
verwenden:
export function init( Module: Array<Partial<Module>>, domApi?: DOMAPI, Optionen?: Optionen ) { // ... Rückgabefunktion patch( oldVnode: VNode |. DocumentFragment, vnode: VNode ): VNode { // ... // Patch startet, Pre-Hook ausführen. for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i](); // ... } }
Hier nehmen wir den pre
-Hook als Beispiel. Die Ausführungszeit des pre
-Hooks ist der Zeitpunkt, zu dem die Ausführung des Patches beginnt. Sie können sehen, dass patch()
zu Beginn der Ausführung zyklisch die in cbs
gespeicherten pre
verknüpften Hooks aufruft. Die Aufrufe anderer Lebenszyklusfunktionen ähneln diesem. Sie können die entsprechenden Lebenszyklusfunktionsaufrufe an anderer Stelle im Quellcode sehen.
Die Gestaltungsidee ist hier das Beobachtermuster . Snabbdom implementiert nicht zum Kern gehörende Funktionen, indem es sie in Modulen verteilt. In Kombination mit der Definition des Lebenszyklus kann das Modul die Hooks definieren, an denen es interessiert ist. Wenn init()
ausgeführt wird, wird es in cbs
Objekte verarbeitet, um diese Hooks zu registrieren. Wenn die Ausführungszeit erreicht ist, rufen Sie auf. Diese Hooks werden verwendet, um die Modulverarbeitung zu benachrichtigen. Dadurch werden der Kerncode und der Modulcode getrennt. Daraus können wir ersehen, dass das Beobachtermuster ein gängiges Muster für die Code-Entkopplung ist.
Als nächstes kommen wir zur Kangkang-Kernfunktion patch()
. Diese Funktion wird nach dem Aufruf von init()
zurückgegeben. Ihre Funktion besteht darin, den VNode zu mounten und zu aktualisieren. Die Signatur lautet wie folgt:
function patch(oldVnode: VNode | Element |. DocumentFragment , vnode: VNode): VNode { // Der Einfachheit halber achten Sie nicht auf DocumentFragment. // ... }
Der oldVnode
-Parameter ist das alte VNode- oder DOM-Element oder Dokumentfragment und der vnode
Parameter ist das aktualisierte Objekt. Hier poste ich direkt eine Beschreibung des Prozesses:
Aufruf des im Modul registrierten pre
Hooks.
Wenn oldVnode
Element
ist, wird es in ein leeres vnode
Objekt konvertiert und elm
wird im Attribut aufgezeichnet.
Die Beurteilung hier ist, ob es sich um Element
handelt (oldVnode as any).nodeType === 1
nodeType === 1
zeigt an, dass es sich um einen ELEMENT_NODE handelt, der hier definiert ist.
Stellen Sie dann fest, ob oldVnode
und vnode
identisch sind. Hier wird sameVnode()
aufgerufen, um Folgendes zu bestimmen:
function sameVnode(vnode1: VNode, vnode2: VNode): boolean { //Gleicher Schlüssel. const isSameKey = vnode1.key === vnode2.key; // Webkomponente, benutzerdefinierter Element-Tag-Name, siehe hier: // https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createElement const isSameIs = vnode1.data?.is === vnode2.data?.is; //Gleicher Selektor. const isSameSel = vnode1.sel === vnode2.sel; // Alle drei sind gleich. return isSameSel && isSameKey && isSameIs; }
patchVnode()
für die Diff-Aktualisierung auf.createElm()
auf, um einen neuen DOM-Knoten zu erstellen. Fügen Sie nach der Erstellung den DOM-Knoten ein und löschen Sie den alten DOM-Knoten.Neue Knoten können eingefügt werden, indem die insert
Hook-Warteschlange aufgerufen wird, die im Vnode-Objekt registriert ist, das an der obigen Operation beteiligt ist, patchVnode()
createElm()
. Warum dies geschieht, wird in createElm()
erwähnt.
Abschließend wird der auf dem Modul registrierte post
Hook aufgerufen.
Der Prozess besteht im Wesentlichen darin, Unterschiede zu machen, wenn die V-Knoten gleich sind, und wenn sie unterschiedlich sind, neue zu erstellen und die alten zu löschen. Schauen wir uns als Nächstes an, wie createElm()
DOM-Knoten erstellt.
createElm()
erstellt einen DOM-Knoten basierend auf der Konfiguration von vnode. Der Vorgang ist wie folgt:
Rufen Sie den init
Hook auf, der möglicherweise im Vnode-Objekt vorhanden ist.
Dann werden wir uns mit mehreren Situationen befassen:
Wenn vnode.sel === '!'
, ist dies die von Snabbdom verwendete Methode, um den ursprünglichen Knoten zu löschen, sodass ein neuer Kommentarknoten eingefügt wird. Da die alten Knoten nach createElm()
gelöscht werden, kann diese Einstellung den Zweck der Deinstallation erreichen.
Wenn die vnode.sel
-Selektordefinition vorhanden ist:
Analysieren Sie den Selektor und rufen Sie id
, tag
und class
ab.
Rufen Sie document.createElement()
oder document.createElementNS
auf, um einen DOM-Knoten zu erstellen, zeichnen Sie ihn in vnode.elm
auf und legen Sie id
, tag
und class
basierend auf den Ergebnissen des vorherigen Schritts fest.
Rufen Sie den create
Hook für das Modul auf.
Verarbeiten Sie das children
Array:
Wenn es sich children
um ein Array handelt, rufen Sie createElm()
rekursiv auf, um den untergeordneten Knoten zu erstellen, und rufen Sie dann appendChild
auf, um ihn unter vnode.elm
bereitzustellen.
Wenn children
kein Array ist, vnode.text
jedoch vorhanden ist, bedeutet dies, dass der Inhalt dieses Elements Text ist. Zu diesem Zeitpunkt wird createTextNode
aufgerufen, um einen Textknoten zu erstellen, und dieser wird unter vnode.elm
gemountet.
Rufen Sie den create
Hook auf dem Vnode auf. Und fügen Sie den insert
Hook auf vnode zur insert
Hook-Warteschlange hinzu.
Die verbleibende Situation besteht darin, dass vnode.sel
nicht vorhanden ist, was darauf hinweist, dass der Knoten selbst ein Text ist. Rufen Sie dann createTextNode
auf, um einen Textknoten zu erstellen, und zeichnen Sie ihn in vnode.elm
auf.
Geben Sie schließlich vnode.elm
zurück.
Aus dem gesamten Prozess ist ersichtlich, dass createElm()
basierend auf verschiedenen Einstellungen sel
Selektors auswählt, wie DOM-Knoten erstellt werden. Hier muss noch ein Detail hinzugefügt werden: die in patch()
erwähnte insert
Hook-Warteschlange. Der Grund, warum diese insert
Hook-Warteschlange benötigt wird, besteht darin, dass sie warten muss, bis das DOM tatsächlich eingefügt wird, bevor sie es ausführt, und dass sie auch warten muss, bis alle untergeordneten Knoten eingefügt sind, damit wir die Größen- und Positionsinformationen des DOM berechnen können Element im insert
um genau zu sein. In Kombination mit dem oben beschriebenen Prozess zum Erstellen von untergeordneten Knoten ist createElm()
ein rekursiver Aufruf zum Erstellen von untergeordneten Knoten, sodass die Warteschlange zuerst die untergeordneten Knoten und dann sich selbst aufzeichnet. Auf diese Weise kann die Reihenfolge beim Ausführen der Warteschlange am Ende von patch()
garantiert werden.
Schauen wir uns als Nächstes an, wie Snabbdom patchVnode()
für Diff verwendet, was den Kern des virtuellen DOM darstellt. Der Verarbeitungsablauf von patchVnode()
ist wie folgt:
Führen Sie zuerst den prepatch
Hook auf vnode aus.
Wenn oldVnode und vnode dieselbe Objektreferenz sind, werden sie ohne Verarbeitung direkt zurückgegeben.
Rufen Sie update
-Hooks für Module und VNodes auf.
Wenn vnode.text
nicht definiert ist, werden mehrere Fälle von children
behandelt:
wenn oldVnode.children
und vnode.children
beide existieren und nicht identisch sind. Rufen Sie dann updateChildren
auf, um zu aktualisieren.
vnode.children
existiert, aber oldVnode.children
existiert nicht. Wenn oldVnode.text
vorhanden ist, löschen Sie es zuerst und rufen Sie dann addVnodes
auf, um neue vnode.children
hinzuzufügen.
vnode.children
existiert nicht, oldVnode.children
jedoch schon. Rufen Sie removeVnodes
auf, um oldVnode.children
zu entfernen.
Wenn weder oldVnode.children
noch vnode.children
vorhanden sind. Löschen Sie oldVnode.text
, falls vorhanden.
Wenn vnode.text
definiert ist und sich von oldVnode.text
unterscheidet. Wenn oldVnode.children
vorhanden ist, rufen Sie removeVnodes
auf, um es zu löschen. Legen Sie dann den Textinhalt über textContent
fest.
Führen Sie abschließend den postpatch
Hook auf dem Vnode aus.
Aus dem Prozess ist ersichtlich, dass die Änderungen an den zugehörigen Attributen der eigenen Knoten in Diff, wie z. B. class
, style
usw., vom Modul aktualisiert werden. Wir werden hier jedoch nicht zu viel erweitern Sie können sich den modulbezogenen Code ansehen. Die Hauptkernverarbeitung von Diff konzentriert sich auf children
. Als nächstes verarbeitet Kangkang Diff mehrere verwandte Funktionen von children
.
ist sehr einfach. Rufen Sie zuerst createElm()
auf, um es zu erstellen, und fügen Sie es dann in das entsprechende übergeordnete Element ein.
destory
remove
destory
, dieser Hook wird zuerst aufgerufen. Die Logik besteht darin, zuerst den Hook für das Vnode-Objekt und dann den Hook für das Modul aufzurufen. Dann wird dieser Hook in dieser Reihenfolge rekursiv auf vnode.children
aufgerufen.remove
: Dieser Hook wird nur ausgelöst, wenn das aktuelle Element aus seinem übergeordneten Element gelöscht wird. Die untergeordneten Elemente im entfernten Element werden nicht ausgelöst und dieser Hook wird sowohl für das Modul als auch für das Vnode-Objekt aufgerufen Zuerst das Modul einschalten und dann vnode aufrufen. Das Besondere ist, dass das Element erst dann tatsächlich entfernt wird, wenn alle remove
aufgerufen wurden. Dadurch können einige verzögerte Löschanforderungen erfüllt werden.Aus dem Obigen ist ersichtlich, dass die Aufruflogik dieser beiden Hooks unterschiedlich ist. Insbesondere wird remove
nur für Elemente aufgerufen, die direkt vom übergeordneten Element getrennt sind.
updateChildren()
wird zum Verarbeiten von Child-Node-Differenzen verwendet und ist auch eine relativ komplexe Funktion in Snabbdom. Die allgemeine Idee besteht darin, insgesamt vier Kopf- und Endzeiger für oldCh
und newCh
zu setzen. Diese vier Zeiger sind oldStartIdx
, oldEndIdx
, newStartIdx
und newEndIdx
. Vergleichen Sie dann die beiden Arrays in while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx)
um dieselben Teile zur Wiederverwendung und Aktualisierung zu finden, und bewegen Sie sich für jeden Vergleich auf ein Zeigerpaar. Der detaillierte Durchlaufprozess wird in der folgenden Reihenfolge verarbeitet:
Wenn einer der vier Zeiger auf vnode == null zeigt, bewegt sich der Zeiger in die Mitte, z. B.: start++ oder end--, das Auftreten von null wird später erläutert.
Wenn der alte und der neue Startknoten gleich sind, das heißt, sameVnode(oldStartVnode, newStartVnode)
gibt true zurück, verwenden Sie patchVnode()
um Diff auszuführen, und beide Startknoten bewegen sich einen Schritt in Richtung Mitte.
Wenn der alte und der neue Endknoten gleich sind, wird auch patchVnode()
verwendet und die beiden Endknoten bewegen sich einen Schritt zurück in die Mitte.
Wenn der alte Startknoten mit dem neuen Endknoten identisch ist, verwenden Sie patchVnode()
um die Aktualisierung zuerst zu verarbeiten. Dann muss der DOM-Knoten, der oldStart entspricht, verschoben werden. Die Verschiebungsstrategie besteht darin, vor dem nächsten Geschwisterknoten des DOM-Knotens zu verschieben, der oldEndVnode
entspricht. Warum bewegt es sich so? Erstens ist oldStart dasselbe wie newEnd, was bedeutet, dass in der aktuellen Schleifenverarbeitung der Startknoten des alten Arrays nach rechts verschoben wird, da bei jeder Verarbeitung die Kopf- und Endzeiger in die Mitte verschoben werden Zu diesem Zeitpunkt wurde oldEnd möglicherweise noch nicht verarbeitet, aber zu diesem Zeitpunkt wurde festgestellt, dass oldStart das letzte in der aktuellen Verarbeitung des neuen Arrays ist, sodass es sinnvoll ist, zum nächsten Geschwister zu wechseln Knoten von oldEnd. Nachdem die Verschiebung abgeschlossen ist, bewegen sich oldStart++ und newEnd-- einen Schritt in die Mitte ihrer jeweiligen Arrays.
Wenn der alte Endknoten mit dem neuen Startknoten übereinstimmt, wird zuerst patchVnode()
verwendet, um die Aktualisierung zu verarbeiten, und dann wird der DOM-Knoten, der oldEnd entspricht, auf den DOM-Knoten verschoben, oldStartVnode
entspricht das Gleiche wie im vorherigen Schritt. Nachdem der Umzug abgeschlossen ist, oldEnd--, newStart++.
Wenn keiner der oben genannten Punkte zutrifft, verwenden Sie den Schlüssel von newStartVnode, um die Index-IDX in oldChildren
zu finden. Abhängig davon, ob der Index vorhanden ist, gibt es zwei verschiedene Verarbeitungslogiken:
Wenn der Index nicht vorhanden ist, bedeutet dies, dass newStartVnode neu erstellt wird. Erstellen Sie mit createElm()
ein neues DOM und fügen Sie es vor dem DOM ein, das oldStartVnode
entspricht.
Wenn der Index vorhanden ist, wird er in zwei Fällen behandelt:
Wenn die Sel der beiden Vnodes unterschiedlich sind, wird er dennoch als neu erstellt betrachtet. Erstellen Sie über createElm()
ein neues DOM und fügen Sie es vor dem DOM ein, das oldStartVnode
entspricht .
Wenn sel gleich ist, wird die Aktualisierung über patchVnode()
verarbeitet und der dem Index von oldChildren
entsprechende Vnode wird auf undefiniert gesetzt. Aus diesem Grund erscheint == null im vorherigen Doppelzeigerdurchlauf. Fügen Sie dann den aktualisierten Knoten in das DOM ein, das oldStartVnode
entspricht.
Nachdem die oben genannten Vorgänge abgeschlossen sind, newStart++.
Nach Abschluss der Durchquerung sind noch zwei Situationen zu bewältigen. Zum einen wurde oldCh
vollständig verarbeitet, aber es gibt immer noch neue Knoten in newCh
und für jeden verbleibenden newCh
muss ein neues DOM erstellt werden. Zum anderen wurde newCh
vollständig verarbeitet und es gibt immer noch alte Knoten in oldCh
. Redundante Knoten müssen entfernt werden. Die beiden Situationen werden wie folgt behandelt:
function updateChildren( parentElm: Knoten, oldCh: VNode[], newCh: VNode[], insertVnodeQueue: VNodeQueue ) { // Doppelzeiger-Traversalprozess. // ... // Es gibt neue Knoten in newCh, die erstellt werden müssen. if (newStartIdx <= newEndIdx) { //Muss vor dem zuletzt verarbeiteten newEndIdx eingefügt werden. before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm; addVnodes( parentElm, vor, newCh, newStartIdx, newEndIdx, insertVnodeQueue ); } // Es gibt immer noch alte Knoten in oldCh, die entfernt werden müssen. if (oldStartIdx <= oldEndIdx) { removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); } }
Schauen wir uns anhand eines praktischen Beispiels den Verarbeitungsprozess von updateChildren()
an:
Der Anfangszustand ist wie folgt, das alte untergeordnete Knotenarray ist [A, B, C] und das neue Knotenarray ist [B, A, C , D]:
In der ersten Vergleichsrunde sind die Start- und Endknoten unterschiedlich, daher prüfen wir, ob newStartVnode im alten Knoten vorhanden ist, und ermitteln die Position von oldCh[1]. Führen Sie dann zuerst patchVnode()
aus, um es zu aktualisieren, und legen Sie dann oldCh[1] fest ] = undefiniert und fügen Sie das DOM vor oldStartVnode
ein, newStartIdx
geht einen Schritt zurück und der Status nach der Verarbeitung ist wie folgt:
In der zweiten Vergleichsrunde sind oldStartVnode
und newStartVnode
gleich. Wenn patchVnode()
zum Aktualisieren ausgeführt wird, verschieben sich oldStartIdx
und newStartIdx
nach der Verarbeitung wie folgt:
In der dritten Vergleichsrunde oldStartVnode == null
bewegt sich oldStartIdx
in die Mitte und der Status wird wie folgt aktualisiert:
In der vierten Vergleichsrunde sind oldStartVnode
und newStartVnode
gleich. Wenn patchVnode()
zum Aktualisieren ausgeführt wird, verschieben sich oldStartIdx
und newStartIdx
nach der Verarbeitung wie folgt:
Zu diesem Zeitpunkt ist oldStartIdx
größer als oldEndIdx
und die Schleife endet. Zu diesem Zeitpunkt gibt es noch neue Knoten, die in newCh
nicht verarbeitet wurden, und Sie müssen addVnodes()
aufrufen, um sie einzufügen. Der endgültige Status ist wie folgt:
ist der Kerninhalt des virtuellen DOM meiner Meinung nach sehr gut. Wenn Sie Zeit haben, können Sie sich die Details des Kangkang-Quellcodes genauer ansehen Ideen sind es wert, gelernt zu werden.