Nodi del DOM virtuale
Cos'è il DOM virtuale
Un albero del DOM virtuale è una struttura dati JavaScript che descrive un albero del DOM. È composto da nodi del DOM virtuale annidati, noti anche come vnode.
La prima volta che un albero del DOM virtuale viene renderizzato, viene utilizzato come modello per creare un albero del DOM che corrisponda alla sua struttura.
In genere, gli alberi del DOM virtuale vengono ricreati a ogni ciclo di rendering, che di solito si attiva in risposta a gestori di eventi o modifiche ai dati. Mithril.js confronta (diffs) un albero di vnode con la sua versione precedente e modifica solo gli elementi DOM in cui sono presenti differenze.
Potrebbe sembrare inefficiente ricreare i vnode così frequentemente, ma i moderni motori JavaScript possono creare centinaia di migliaia di oggetti in meno di un millisecondo. D'altra parte, la modifica del DOM è significativamente più costosa rispetto alla creazione di vnode.
Per questo motivo, Mithril.js utilizza un algoritmo di differenziazione del DOM virtuale sofisticato e altamente ottimizzato per ridurre al minimo gli aggiornamenti del DOM. Mithril.js genera strutture dati vnode progettate con cura, che vengono compilate dai motori JavaScript per prestazioni di accesso ai dati quasi native. Inoltre, Mithril.js ottimizza in modo aggressivo anche la funzione che crea i vnode.
Mithril.js dedica così tanti sforzi a supportare un modello di rendering che ricrea l'intero albero del DOM virtuale a ogni rendering per fornire un'API dichiarativa in modalità immediata, uno stile di rendering che semplifica drasticamente la gestione della complessità dell'interfaccia utente.
Per illustrare perché la modalità immediata è così importante, si consideri l'API DOM e HTML. L'API DOM è un'API imperativa in modalità mantenuta e richiede: 1. la scrittura di istruzioni precise per costruire un albero DOM in modo procedurale e 2. la scrittura di altre istruzioni per aggiornare tale albero. La natura imperativa dell'API DOM offre molte opportunità per micro-ottimizzare il codice, ma aumenta anche il rischio di introdurre bug e di rendere il codice più difficile da comprendere.
Al contrario, HTML è più vicino a un sistema di rendering in modalità immediata. Con HTML, puoi scrivere un albero DOM in modo molto più naturale e leggibile, senza doverti preoccupare di aggiungere un nodo figlio all'elemento genitore o di incorrere in stack overflow quando si renderizzano alberi estremamente profondi, ecc.
Il DOM virtuale fa un ulteriore passo avanti rispetto all'HTML, consentendoti di scrivere alberi DOM dinamici senza dover scrivere manualmente insiemi di chiamate all'API DOM per sincronizzare in modo efficiente l'interfaccia utente con modifiche di dati arbitrarie.
Nozioni di base
I nodi del DOM virtuale, o vnode, sono oggetti JavaScript che rappresentano elementi del DOM (o parti di esso). Il motore del DOM virtuale di Mithril.js utilizza un albero di vnode per produrre un albero DOM.
I vnode vengono creati tramite l'utilità hyperscript m()
:
m('div', { id: 'test' }, 'hello');
Hyperscript può anche utilizzare componenti:
// definisci un componente
var ExampleComponent = {
view: function (vnode) {
return m('div', vnode.attrs, ['Hello ', vnode.children]);
},
};
// utilizzalo
m(ExampleComponent, { style: 'color:red;' }, 'world');
// HTML equivalente:
// <div style="color:red;">Hello world</div>
Struttura
I nodi del DOM virtuale, o vnode, sono oggetti JavaScript che rappresentano un elemento (o parti del DOM) e hanno le seguenti proprietà:
Proprietà | Tipo | Descrizione |
---|---|---|
tag | String|Object | Il nodeName di un elemento DOM. Può anche essere la stringa [ se un vnode è un frammento, # se è un vnode di testo o < se è un vnode HTML affidabile. Inoltre, può essere un componente. |
key | String? | Il valore utilizzato per mappare un elemento DOM alla sua voce corrispondente in un array di dati. |
attrs | Object? | Una hashmap di attributi DOM, eventi, proprietà e metodi del ciclo di vita. |
children | (Array|String|Number|Boolean)? | Nella maggior parte dei tipi di vnode, la proprietà children è un array di vnode. Per i vnode di testo e HTML affidabili, la proprietà children è una stringa, un numero o un booleano. |
text | (String|Number|Boolean)? | Viene utilizzato al posto di children se il vnode contiene un nodo di testo come unico figlio. Questo viene fatto per motivi di prestazioni. I vnode dei componenti non utilizzano mai la proprietà text anche se hanno un nodo di testo come loro unico figlio. |
dom | Element? | Punta all'elemento che corrisponde al vnode. Questa proprietà è undefined nel metodo del ciclo di vita oninit . Nei frammenti e nei vnode HTML affidabili, dom punta al primo elemento nell'intervallo. |
domSize | Number? | È impostato solo nei frammenti e nei vnode HTML affidabili, mentre è undefined in tutti gli altri tipi di vnode. Definisce il numero di elementi DOM che il vnode rappresenta (a partire dall'elemento a cui fa riferimento la proprietà dom ). |
state | Object? | Un oggetto che viene mantenuto tra i ridisegni. Viene fornito dal motore principale quando necessario. Nei vnode dei componenti POJO, lo state eredita dal prototipo dell'oggetto/classe del componente. Nei vnode dei componenti di classe è un'istanza della classe. Nei componenti di chiusura è l'oggetto restituito dalla chiusura. |
events | Object? | Un oggetto che viene mantenuto tra i ridisegni e che memorizza i gestori di eventi in modo che possano essere rimossi utilizzando l'API DOM. La proprietà events è undefined se non sono definiti gestori di eventi. Questa proprietà viene utilizzata solo internamente da Mithril.js, non utilizzarla o modificarla. |
instance | Object? | Per i componenti, una posizione di archiviazione per il valore restituito dalla view . Questa proprietà viene utilizzata solo internamente da Mithril.js, non utilizzarla o modificarla. |
Tipi di vnode
La proprietà tag
di un vnode determina il suo tipo. Esistono cinque tipi di vnode:
Tipo di vnode | Esempio | Descrizione |
---|---|---|
Elemento | {tag: "div"} | Rappresenta un elemento DOM. |
Frammento | {tag: "[", children: []} | Rappresenta una lista di elementi DOM il cui elemento DOM padre può contenere anche altri elementi che non sono nel frammento. Quando si utilizza la funzione helper m() , i vnode frammento possono essere creati solo annidando array nel parametro children di m() . m("[") non crea un vnode valido. |
Testo | {tag: "#", children: ""} | Rappresenta un nodo di testo DOM. |
HTML affidabile | {tag: "<", children: "<br>"} | Rappresenta una lista di elementi DOM da una stringa HTML. |
Componente | {tag: ExampleComponent} | Se tag è un oggetto JavaScript con un metodo view , il vnode rappresenta il DOM generato dal rendering del componente. |
Tutto in un albero del DOM virtuale è un vnode, incluso il testo. L'utility m()
normalizza automaticamente il suo argomento children
e trasforma le stringhe in vnode di testo e gli array annidati in vnode frammento.
Solo i nomi dei tag degli elementi e i componenti possono essere il primo argomento della funzione m()
. In altre parole, [
, #
e <
non sono argomenti selector
validi per m()
. I vnode HTML affidabili possono essere creati tramite m.trust()
Classe monomorfica
Il modulo mithril/render/vnode
viene utilizzato da Mithril.js per generare tutti i vnode. Ciò garantisce che i moderni motori JavaScript possano ottimizzare la differenziazione del DOM virtuale compilando sempre i vnode nella stessa classe nascosta.
Quando si creano librerie che emettono vnode, è necessario utilizzare questo modulo invece di scrivere oggetti JavaScript semplici per garantire un elevato livello di prestazioni di rendering.
Evita anti-pattern
Evita di riutilizzare i vnode
I vnode dovrebbero rappresentare lo stato del DOM in un determinato momento. Il motore di rendering di Mithril.js presuppone che un vnode riutilizzato sia invariato, quindi la modifica di un vnode utilizzato in un rendering precedente comporterà un comportamento non definito.
È possibile riutilizzare i vnode sul posto per impedire una differenziazione, ma è preferibile utilizzare onbeforeupdate
.
Evita di passare i dati del modello direttamente ai componenti tramite attributi
La proprietà key
potrebbe apparire nel tuo modello di dati in un modo che potrebbe entrare in conflitto con la logica di gestione delle chiavi di Mithril.js, e il tuo modello potrebbe essere esso stesso un'istanza mutabile con un metodo che condivide un nome con un hook del ciclo di vita come onupdate
o onremove
. Ad esempio, un modello potrebbe utilizzare una proprietà key
per rappresentare una chiave di colore personalizzabile. Quando questo cambia, può portare i componenti a ricevere dati errati, a cambiare posizione inaspettatamente o ad altri comportamenti imprevisti e indesiderati. Invece, passalo come attributo in modo che Mithril.js non lo interpreti in modo errato (e in modo da poterlo potenzialmente mutare o chiamare metodi prototipo su di esso in seguito):
// Modello di dati
var users = [
{ id: 1, name: 'John', key: 'red' },
{ id: 2, name: 'Mary', key: 'blue' },
];
// Successivamente...
users[0].key = 'yellow';
// EVITARE
users.map(function (user) {
// Il componente di John verrà distrutto e ricreato
return m(UserComponent, user);
});
// PREFERIRE
users.map(function (user) {
// La chiave viene estratta appositamente: al modello di dati viene assegnata una sua proprietà
return m(UserComponent, { key: user.id, model: user });
});
Evita le istruzioni nei metodi view
Le istruzioni JavaScript nei metodi view spesso richiedono di modificare la struttura naturalmente annidata di un albero HTML, rendendo il codice più verboso e meno leggibile.
// EVITARE
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);
},
};
Invece, preferisci utilizzare espressioni JavaScript come l'operatore ternario per il rendering condizionale e i metodi degli array per gestire le liste.
// PREFERIRE
var BetterListComponent = {
view: function (vnode) {
return m(
'ul',
vnode.attrs.items.map(function (item) {
return m('li', item);
})
);
},
};