Virtuelle DOM Knoten
Was ist virtuelles DOM?
Ein virtueller DOM-Baum ist eine Datenstruktur in JavaScript, die einen DOM-Baum repräsentiert. Er besteht aus verschachtelten virtuellen DOM-Knoten, auch bekannt als Vnodes.
Beim erstmaligen Rendern eines virtuellen DOM-Baums dient dieser als Vorlage für einen DOM-Baum, der seiner Struktur entspricht.
Typischerweise werden virtuelle DOM-Bäume in jedem Rendering-Zyklus neu erstellt, meist als Reaktion auf Ereignis-Handler oder Datenänderungen. Mithril.js vergleicht einen Vnode-Baum mit seiner vorherigen Version und modifiziert nur DOM-Elemente an Stellen, an denen Änderungen vorhanden sind.
Es mag verschwenderisch erscheinen, Vnodes so häufig neu zu erstellen, aber moderne JavaScript-Engines können Hunderttausende Objekte in weniger als einer Millisekunde erstellen. Andererseits ist das Modifizieren des DOM um ein Vielfaches teurer als das Erstellen von Vnodes.
Aus diesem Grund verwendet Mithril.js einen ausgeklügelten und hochoptimierten virtuellen DOM-Diff-Algorithmus, um die Anzahl der DOM-Aktualisierungen zu minimieren. Mithril.js generiert auch sorgfältig erstellte Vnode-Datenstrukturen, die von JavaScript-Engines für nahezu native Zugriffsgeschwindigkeit auf die Datenstrukturen optimiert werden. Darüber hinaus optimiert Mithril.js die Vnode-Erzeugungsfunktion intensiv.
Der Grund, warum Mithril.js so viel Aufwand betreibt, um ein Rendering-Modell zu unterstützen, das den gesamten virtuellen DOM-Baum bei jedem Rendern neu erstellt, ist die Bereitstellung einer deklarativen Immediate Mode API, eine Art des Renderings, die es drastisch einfacher macht, die UI-Komplexität zu verwalten.
Um zu veranschaulichen, warum der Immediate Mode so wichtig ist, betrachten Sie die DOM-API und HTML. Die DOM-API ist eine imperative Retained Mode API und erfordert 1. das explizite Schreiben genauer Anweisungen, um einen DOM-Baum prozedural zusammenzusetzen, und 2. das Schreiben weiterer Anweisungen, um diesen Baum zu aktualisieren. Die imperative Natur der DOM-API bedeutet, dass Sie viele Möglichkeiten haben, Ihren Code zu mikro-optimieren, aber es bedeutet auch, dass Sie mehr Chancen haben, Fehler einzuführen und den Code schwerer verständlich zu machen.
Im Gegensatz dazu ähnelt HTML eher einem Immediate Mode Rendering-System. Mit HTML können Sie DOM-Bäume auf natürlichere und besser lesbare Weise schreiben, ohne sich Sorgen machen zu müssen, dass Sie vergessen, ein Kind an ein Elternteil anzuhängen, oder dass es zu Stack-Überläufen kommt, wenn Sie extrem tiefe Bäume rendern usw.
Virtual DOM geht über HTML hinaus, indem es Ihnen ermöglicht, dynamische DOM-Bäume zu schreiben, ohne manuell mehrere DOM-API-Aufrufe zur effizienten Synchronisierung der UI mit beliebigen Datenänderungen schreiben zu müssen.
Grundlagen
Virtuelle DOM-Knoten oder Vnodes sind JavaScript-Objekte, die DOM-Elemente (oder Teile des DOM) darstellen. Die Virtual DOM-Engine von Mithril.js verwendet einen Baum von Vnodes, um einen DOM-Baum zu erzeugen.
Vnodes werden über das Hyperscript-Werkzeug m()
](../api/hyperscript.md) erstellt:
m('div', { id: 'test' }, 'hello');
Hyperscript kann auch Komponenten verarbeiten:
// Definition einer Komponente
var ExampleComponent = {
view: function (vnode) {
return m('div', vnode.attrs, ['Hello ', vnode.children]);
},
};
// Verwendung der Komponente
m(ExampleComponent, { style: 'color:red;' }, 'world');
// Entsprechender HTML-Code:
// <div style="color:red;">Hello world</div>
Struktur
Virtuelle DOM-Knoten oder Vnodes sind JavaScript-Objekte, die ein Element (oder Teile des DOM) darstellen und die folgenden Eigenschaften haben:
Eigenschaft | Typ | Beschreibung |
---|---|---|
tag | String|Object | Der nodeName eines DOM-Elements. Es kann auch die Zeichenkette [ sein, wenn ein Vnode ein Fragment ist, # , wenn es ein Text-Vnode ist, oder < , wenn es ein Trusted-HTML-Vnode ist. Zusätzlich kann es eine Komponente sein. |
key | String? | Der Wert, der verwendet wird, um ein DOM-Element seinem jeweiligen Element in einem Array von Daten zuzuordnen. |
attrs | Object? | Eine Zuordnung mit DOM-Attributen, Ereignissen, Eigenschaften und Lebenszyklus-Methoden. |
children | (Array|String|Number|Boolean)? | Bei den meisten Vnode-Typen ist die Eigenschaft children ein Array von Vnodes. Für Text- und Trusted-HTML-Vnodes ist die Eigenschaft children entweder eine Zeichenkette, eine Zahl oder ein Wahrheitswert. |
text | (String|Number|Boolean)? | Dies wird anstelle von children verwendet, wenn ein Vnode einen Textknoten als einziges Kind enthält. Dies geschieht aus Performance-Gründen. Komponenten-Vnodes verwenden niemals die Eigenschaft text , selbst wenn sie einen Textknoten als einziges Kind haben. |
dom | Element? | Verweist auf das Element, das dem Vnode entspricht. Diese Eigenschaft ist in der Lebenszyklus-Methode oninit undefined . In Fragmenten und Trusted-HTML-Vnodes verweist dom auf das erste Element im Bereich. |
domSize | Number? | Dies wird nur in Fragment- und Trusted-HTML-Vnodes gesetzt und ist in allen anderen Vnode-Typen undefined . Es definiert die Anzahl der DOM-Elemente, die der Vnode darstellt (beginnend mit dem Element, auf das die Eigenschaft dom verweist). |
state | Object? | Ein Objekt, das zwischen Neuzeichnungen persistent ist. Es wird von der Core-Engine bei Bedarf bereitgestellt. In POJO-Komponenten-Vnodes erbt state prototypisch vom Komponentenobjekt/-klasse. In Klassenkomponenten-Vnodes ist es eine Instanz der Klasse. In Closure-Komponenten ist es das Objekt, das von der Closure zurückgegeben wird. |
events | Object? | Ein Objekt, das zwischen Neuzeichnungen persistent ist und die Event-Handler speichert, so dass sie mit der DOM-API entfernt werden können. Die Eigenschaft events ist undefined , wenn keine Event-Handler definiert sind. Diese Eigenschaft wird nur intern von Mithril.js verwendet, nicht verwenden oder modifizieren. |
instance | Object? | Für Komponenten ein Speicherort für den Wert, der von der view zurückgegeben wird. Diese Eigenschaft wird nur intern von Mithril.js verwendet, nicht verwenden oder modifizieren. |
Vnode-Typen
Die Eigenschaft tag
eines Vnodes definiert seinen Typ. Es gibt fünf Vnode-Typen:
Vnode-Typ | Beispiel | Beschreibung |
---|---|---|
Element | {tag: "div"} | Stellt ein DOM-Element dar. |
Fragment | {tag: "[", children: []} | Stellt eine Liste von DOM-Elementen dar, deren übergeordnetes DOM-Element auch andere Elemente enthalten kann, die nicht im Fragment enthalten sind. Fragment-Vnodes können mit der Hilfsfunktion m() nur erstellt werden, indem Arrays in den children -Parameter von m() verschachtelt werden. m("[") erstellt keinen gültigen Vnode. |
Text | {tag: "#", children: ""} | Stellt einen DOM-Textknoten dar. |
Trusted HTML | {tag: "<", children: "<br>"} | Stellt eine Liste von DOM-Elementen aus einem HTML-String dar. |
Komponente | {tag: ExampleComponent} | Wenn tag ein JavaScript-Objekt mit einer view -Methode ist, stellt der Vnode das DOM dar, das durch das Rendern der Komponente generiert wird. |
Alles in einem virtuellen DOM-Baum ist ein Vnode, einschließlich Text. Das Utility m()
normalisiert automatisch sein children
-Argument und wandelt Strings in Text-Vnodes und verschachtelte Arrays in Fragment-Vnodes um.
Nur Element-Tag-Namen und Komponenten können das erste Argument der Funktion m()
sein. Mit anderen Worten, [
, #
und <
sind keine gültigen selector
-Argumente für m()
. Trusted-HTML-Vnodes können über m.trust()
erstellt werden.
Monomorphe Klasse
Das Modul mithril/render/vnode
wird von Mithril.js verwendet, um alle Vnodes zu generieren. Dies stellt sicher, dass moderne JavaScript-Engines das virtuelle DOM-Diffing optimieren können, indem sie Vnodes immer in dieselbe Hidden Class kompilieren.
Wenn Sie Bibliotheken erstellen, die Vnodes ausgeben, sollten Sie dieses Modul verwenden, anstatt nackte JavaScript-Objekte zu schreiben, um ein hohes Maß an Rendering-Performance zu gewährleisten.
Anti-Patterns vermeiden
Vermeiden Sie die Wiederverwendung von Vnodes
Vnodes sollen den Zustand des DOM zu einem bestimmten Zeitpunkt darstellen. Die Rendering-Engine von Mithril.js geht davon aus, dass ein wiederverwendeter Vnode unverändert ist. Das Ändern eines Vnodes, der in einem vorherigen Render verwendet wurde, führt daher zu undefiniertem Verhalten.
Es ist möglich, Vnodes an Ort und Stelle wiederzuverwenden, um einen Diff zu vermeiden, aber es ist vorzuziehen, onbeforeupdate
zu verwenden.
Vermeiden Sie, Modelldaten direkt über Attribute an Komponenten zu übergeben
Die key
-Eigenschaft kann in Ihrem Datenmodell so vorkommen, dass sie mit der Key-Logik von Mithril.js konfligiert. Ihr Modell könnte auch eine mutable Instanz mit einer Methode sein, die denselben Namen wie ein Lebenszyklus-Hook hat, z.B. onupdate
oder onremove
. Beispielsweise könnte ein Modell eine key
-Eigenschaft verwenden, um einen anpassbaren Farbschlüssel darzustellen. Wenn sich dies ändert, kann dies dazu führen, dass Komponenten falsche Daten empfangen, unerwartet die Position ändern oder anderes unerwartetes, unerwünschtes Verhalten zeigen. Übergeben Sie es stattdessen als Attribut, damit Mithril.js es nicht falsch interpretiert und Sie es später möglicherweise noch mutieren oder Prototypmethoden darauf aufrufen können:
// Datenmodell
var users = [
{ id: 1, name: 'John', key: 'red' },
{ id: 2, name: 'Mary', key: 'blue' },
];
// Später...
users[0].key = 'yellow';
// VERMEIDEN
users.map(function (user) {
// Die Komponente für John wird zerstört und neu erstellt
return m(UserComponent, user);
});
// BEVORZUGEN
users.map(function (user) {
// Key wird explizit extrahiert: Das Datenmodell erhält eine eigene Eigenschaft
return m(UserComponent, { key: user.id, model: user });
});
Vermeiden Sie Anweisungen in View-Methoden
JavaScript-Anweisungen in View-Methoden erfordern oft eine Änderung der natürlich verschachtelten Struktur eines HTML-Baums, was den Code ausführlicher und weniger lesbar macht.
// VERMEIDEN
var BadListComponent = {
view: function (vnode) {
var list = [];
for (var i = 0; i < vnode.attrs.items.length; i++) {
list.push(m('li', vnode.attrs.items[i]));
}
return m('ul', list);
},
};
Verwenden Sie stattdessen lieber JavaScript-Ausdrücke wie den ternären Operator für bedingtes Rendern und Array-Methoden für listenähnliche Strukturen.
// BEVORZUGEN
var BetterListComponent = {
view: function (vnode) {
return m(
'ul',
vnode.attrs.items.map(function (item) {
return m('li', item);
})
);
},
};