Nœuds du DOM virtuel
Qu'est-ce que le DOM virtuel ?
Un arbre DOM virtuel est une structure de données JavaScript qui décrit un arbre DOM. Il est constitué de nœuds DOM virtuels imbriqués, également appelés vnodes.
Lorsqu'un arbre DOM virtuel est rendu pour la première fois, il sert de plan directeur pour créer un arbre DOM qui correspond à sa structure.
En général, les arbres DOM virtuels sont ensuite recréés à chaque cycle de rendu, ce qui se produit normalement en réponse à des gestionnaires d'événements ou à des modifications de données. Mithril.js compare un arbre de vnodes à sa version précédente et ne modifie les éléments DOM que là où des changements sont détectés.
Il peut sembler superflu de recréer aussi fréquemment des vnodes, mais il s'avère que les moteurs JavaScript modernes peuvent créer des centaines de milliers d'objets en moins d'une milliseconde. D'un autre côté, la modification du DOM est de plusieurs ordres de grandeur plus coûteuse que la création de vnodes.
C'est pourquoi Mithril.js utilise un algorithme de diff de DOM virtuel sophistiqué et hautement optimisé pour minimiser le nombre de mises à jour du DOM. Mithril.js génère aussi des structures de données vnode minutieusement élaborées qui sont compilées par les moteurs JavaScript pour des performances d'accès aux structures de données quasi-natives. De plus, Mithril.js optimise également de manière agressive la fonction qui crée des vnodes.
La raison pour laquelle Mithril.js se donne tant de mal pour prendre en charge un modèle de rendu qui recrée l'ensemble de l'arbre DOM virtuel à chaque rendu est de fournir une API mode immédiat (immediate mode) déclarative, un style de rendu qui facilite considérablement la gestion de la complexité de l'interface utilisateur.
Pour illustrer l'importance du mode immédiat, considérez l'API DOM et HTML. L'API DOM est une API mode retenu impérative qui nécessite 1) d'écrire des instructions exactes pour assembler un arbre DOM de manière procédurale, et 2) d'écrire d'autres instructions pour mettre à jour cet arbre. La nature impérative de l'API DOM signifie que vous avez de nombreuses occasions de micro-optimiser votre code, mais cela signifie également que vous avez plus de chances d'introduire des bogues et de rendre le code plus difficile à comprendre.
En revanche, HTML est plus proche d'un système de rendu en mode immédiat. Avec HTML, vous pouvez écrire un arbre DOM d'une manière beaucoup plus naturelle et lisible, sans vous soucier d'oublier d'ajouter un enfant à un parent, de rencontrer des débordements de pile lors du rendu d'arbres de très grande profondeur, etc.
Le DOM virtuel va encore plus loin que HTML en vous permettant d'écrire des arbres DOM dynamiques sans avoir à écrire manuellement plusieurs ensembles d'appels d'API DOM pour synchroniser efficacement l'interface utilisateur avec des modifications de données arbitraires.
Bases
Les nœuds du DOM virtuel, ou vnodes, sont des objets JavaScript qui représentent des éléments DOM (ou des parties d'un DOM). Le moteur de DOM virtuel de Mithril.js consomme un arbre de vnodes pour produire un arbre DOM.
Les vnodes sont créés par l'utilitaire hyperscript m()
:
m('div', { id: 'test' }, 'hello');
Hyperscript peut également consommer des composants :
// définition d'un composant
var ExampleComponent = {
view: function (vnode) {
return m('div', vnode.attrs, ['Hello ', vnode.children]);
},
};
// l'utiliser
m(ExampleComponent, { style: 'color:red;' }, 'world');
// HTML équivalent :
// <div style="color:red;">Hello world</div>
Structure
Les nœuds du DOM virtuel, ou vnodes, sont des objets JavaScript qui représentent un élément (ou des parties du DOM) et ont les propriétés suivantes :
Propriété | Type | Description |
---|---|---|
tag | String|Object | Nom du nœud d'un élément DOM. Peut aussi être [ pour un fragment, # pour un nœud texte, ou < pour un nœud HTML approuvé. Peut également être un composant. |
key | String? | Valeur utilisée pour associer un élément DOM à son élément respectif dans un tableau de données. |
attrs | Object? | Table de hachage des attributs DOM, des événements, des propriétés et des méthodes de cycle de vie. |
children | (Array|String|Number|Boolean)? | Dans la plupart des types de vnode, la propriété children est un tableau de vnodes. Pour les vnodes de texte et HTML approuvés, la propriété children est soit une chaîne de caractères, soit un nombre, soit un booléen. |
text | (String|Number|Boolean)? | Utilisé à la place de children si un vnode contient un nœud de texte comme seul enfant. Ceci est fait pour des raisons de performance. Les vnodes de composant n'utilisent jamais la propriété text , même s'ils ont un nœud de texte comme seul enfant. |
dom | Element? | Pointeur vers l'élément correspondant au vnode. Cette propriété est undefined dans la méthode de cycle de vie oninit . Dans les fragments et les vnodes HTML approuvés, dom pointe vers le premier élément de la plage. |
domSize | Number? | Définit le nombre d'éléments DOM que le vnode représente (à partir de l'élément référencé par la propriété dom ). Défini uniquement dans les fragments et les vnodes HTML approuvés, et undefined dans tous les autres types de vnode. |
state | Object? | Objet conservé entre les redessinages. Fourni par le moteur central lorsque nécessaire. Dans les vnodes de composant POJO, le state hérite prototypiquement de l'objet/classe du composant. Dans les vnodes de composant de classe, il s'agit d'une instance de la classe. Dans les composants de fermeture, il s'agit de l'objet renvoyé par la fermeture. |
events | Object? | Objet conservé entre les redessinages et stockant les gestionnaires d'événements afin qu'ils puissent être supprimés à l'aide de l'API DOM. La propriété events est undefined s'il n'y a pas de gestionnaires d'événements définis. Cette propriété est utilisée uniquement en interne par Mithril.js, ne l'utilisez pas et ne la modifiez pas. |
instance | Object? | Pour les composants, emplacement de stockage de la valeur renvoyée par la view . Cette propriété est utilisée uniquement en interne par Mithril.js, ne l'utilisez pas et ne la modifiez pas. |
Types de vnode
La propriété tag
d'un vnode détermine son type. Il existe cinq types de vnode :
Type de vnode | Exemple | Description |
---|---|---|
Élément | {tag: "div"} | Représente un élément DOM. |
Fragment | {tag: "[", children: []} | Représente une liste d'éléments DOM dont l'élément DOM parent peut également contenir d'autres éléments qui ne sont pas dans le fragment. Lors de l'utilisation de la fonction d'assistance m() , les vnodes de fragment ne peuvent être créés qu'en imbriquant des tableaux dans le paramètre children de m() . m("[") ne crée pas un vnode valide. |
Texte | {tag: "#", children: ""} | Représente un nœud de texte DOM. |
HTML approuvé | {tag: "<", children: "<br>"} | Représente une liste d'éléments DOM à partir d'une chaîne HTML. |
Composant | {tag: ExampleComponent} | Si tag est un objet JavaScript avec une méthode view , le vnode représente le DOM généré par le rendu du composant. |
Tout dans un arbre DOM virtuel est un vnode, y compris le texte. L'utilitaire m()
normalise automatiquement son argument children
et transforme les chaînes en vnodes de texte et les tableaux imbriqués en vnodes de fragment.
Seuls les noms de balises d'élément et les composants peuvent être le premier argument de la fonction m()
. En d'autres termes, [
, #
et <
ne sont pas des arguments selector
valides pour m()
. Les vnodes HTML approuvés peuvent être créés via m.trust()
Classe monomorphe
Le module mithril/render/vnode
est utilisé par Mithril.js pour générer tous les vnodes. Cela garantit que les moteurs JavaScript modernes peuvent optimiser le diff de DOM virtuel en compilant toujours les vnodes dans la même classe cachée.
Lors de la création de bibliothèques qui émettent des vnodes, vous devez utiliser ce module au lieu d'écrire des objets JavaScript nus afin de garantir un niveau élevé de performances de rendu.
Éviter les anti-modèles
Éviter de réutiliser les vnodes
Les vnodes sont censés représenter l'état du DOM à un certain moment dans le temps. Le moteur de rendu de Mithril.js suppose qu'un vnode réutilisé est inchangé. Par conséquent, modifier un vnode qui a été utilisé lors d'un rendu précédent entraînera un comportement indéfini.
Il est possible de réutiliser des vnodes sur place pour éviter un diff, mais il est préférable d'utiliser onbeforeupdate
.
Éviter de transmettre les données du modèle directement aux composants via des attributs
La propriété key
peut apparaître dans votre modèle de données d'une manière qui entre en conflit avec la logique de clé de Mithril.js. De plus, votre modèle peut lui-même être une instance mutable avec une méthode qui partage un nom avec un hook de cycle de vie comme onupdate
ou onremove
. Par exemple, un modèle peut utiliser une propriété key
pour représenter une clé de couleur personnalisable. Lorsque cela change, cela peut entraîner la réception de données incorrectes par les composants, la modification inattendue des positions ou d'autres comportements inattendus et indésirables. Au lieu de cela, transmettez-le en tant qu'attribut afin que Mithril.js ne l'interprète pas mal (et que vous puissiez potentiellement le modifier ou appeler des méthodes de prototype ultérieurement) :
// Modèle de données
var users = [
{ id: 1, name: 'John', key: 'red' },
{ id: 2, name: 'Mary', key: 'blue' },
];
// Plus tard...
users[0].key = 'yellow';
// À ÉVITER
users.map(function (user) {
// Le composant pour John sera détruit et recréé
return m(UserComponent, user);
});
// PRÉFÉRER
users.map(function (user) {
// La clé est spécifiquement extraite : le modèle de données reçoit sa propre propriété
return m(UserComponent, { key: user.id, model: user });
});
Éviter les instructions dans les méthodes de vue
Les instructions JavaScript dans les méthodes de vue nécessitent souvent de modifier la structure naturellement imbriquée d'un arbre HTML, ce qui rend le code plus verbeux et moins lisible.
// À ÉVITER
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);
},
};
Au lieu de cela, préférez utiliser des expressions JavaScript telles que l'opérateur ternaire pour le rendu conditionnel et les méthodes Array pour les structures de type liste.
// PRÉFÉRER
var BetterListComponent = {
view: function (vnode) {
return m(
'ul',
vnode.attrs.items.map(function (item) {
return m('li', item);
})
);
},
};