Nodos del DOM virtual
¿Qué es el DOM virtual?
Un árbol DOM virtual es una estructura de datos en JavaScript que describe un árbol DOM. Consiste en nodos DOM virtuales anidados, también conocidos como vnodes.
La primera vez que se renderiza un árbol del DOM virtual, se usa como plantilla para crear un árbol del DOM que coincida con su estructura.
Normalmente, los árboles del DOM virtual se recrean en cada ciclo de renderizado, lo que suele ocurrir en respuesta a los controladores de eventos o a los cambios de datos. Mithril.js compara (diff) un árbol de vnodes con su versión anterior y modifica únicamente los elementos DOM donde existen cambios.
Recrear los vnodes con tanta frecuencia podría parecer un desperdicio; sin embargo, los motores de JavaScript modernos pueden crear cientos de miles de objetos en menos de un milisegundo. No obstante, modificar el DOM es órdenes de magnitud más costoso que crear vnodes.
Por ello, Mithril.js utiliza un algoritmo de diferenciación del DOM virtual sofisticado y optimizado para minimizar la cantidad de actualizaciones del DOM. Mithril.js también genera estructuras de datos de vnodes muy optimizadas que son compiladas por los motores de JavaScript para un rendimiento de acceso a datos casi nativo. Además, Mithril.js optimiza agresivamente la función que crea vnodes.
Mithril.js realiza este esfuerzo porque admite un modelo de renderizado que recrea todo el árbol DOM virtual en cada renderizado para proporcionar una API declarativa de modo inmediato, un estilo de renderizado que facilita drásticamente la gestión de la complejidad de la interfaz de usuario.
Para entender la importancia del modo inmediato, considere la API del DOM y HTML. La API del DOM sigue un modo retenido imperativo y requiere: 1. escribir instrucciones exactas para construir un árbol del DOM proceduralmente, y 2. escribir otras instrucciones para actualizar ese árbol. La naturaleza imperativa de la API del DOM ofrece muchas oportunidades para micro-optimizar el código, pero también aumenta las posibilidades de introducir errores y dificulta la comprensión del código.
En cambio, HTML se asemeja más a un sistema de renderizado en modo inmediato. Con HTML, puede escribir un árbol del DOM de una manera mucho más natural y legible, sin preocuparse por olvidar agregar un elemento hijo a su padre, encontrarse con desbordamientos de pila al renderizar árboles extremadamente profundos, etc.
El DOM virtual va un paso más allá de HTML, permitiendo escribir árboles DOM dinámicos sin tener que escribir manualmente múltiples llamadas a la API del DOM para sincronizar eficientemente la interfaz de usuario con cambios de datos arbitrarios.
Conceptos básicos
Los nodos DOM virtuales, o vnodes, son objetos de JavaScript que representan elementos DOM (o partes del DOM). El motor de DOM virtual de Mithril.js procesa un árbol de vnodes para producir un árbol del DOM.
Los vnodes se crean mediante la utilidad hyperscript m()
:
m('div', { id: 'test' }, 'hello');
Hyperscript también puede utilizar componentes:
// define un componente
var ExampleComponent = {
view: function (vnode) {
return m('div', vnode.attrs, ['Hello ', vnode.children]);
},
};
// utilízalo
m(ExampleComponent, { style: 'color:red;' }, 'world');
// HTML equivalente:
// <div style="color:red;">Hello world</div>
Estructura
Los nodos DOM virtuales, o vnodes, son objetos de JavaScript que representan un elemento (o partes del DOM) y tienen las siguientes propiedades:
Propiedad | Tipo | Descripción |
---|---|---|
tag | String|Object | El nodeName de un elemento DOM. También puede ser la cadena [ si un vnode es un fragmento, # si es un vnode de texto, o < si es un vnode de HTML confiable. Además, puede ser un componente. |
key | String? | El valor utilizado para mapear un elemento DOM a su elemento respectivo en una matriz de datos. |
attrs | Object? | Un objeto con atributos del DOM, eventos, propiedades y métodos de ciclo de vida. |
children | (Array|String|Number|Boolean)? | En la mayoría de los tipos de vnode, la propiedad children es una matriz de vnodes. Para los vnodes de texto y HTML confiable, la propiedad children es una cadena, un número o un booleano. |
text | (String|Number|Boolean)? | Se usa en lugar de children cuando un vnode contiene un nodo de texto como su único hijo. Esto se hace por razones de rendimiento. Los vnodes de componente nunca usan la propiedad text incluso si tienen un nodo de texto como su único hijo. |
dom | Element? | Referencia al elemento DOM correspondiente al vnode. Esta propiedad es undefined en el método de ciclo de vida oninit . En fragmentos y vnodes de HTML confiable, dom apunta al primer elemento en el rango. |
domSize | Number? | Solo se define para fragmentos y vnodes de HTML confiable, y es undefined en todos los demás tipos de vnode. Define el número de elementos del DOM que representa el vnode (comenzando desde el elemento al que hace referencia la propiedad dom ). |
state | Object? | Un objeto que se conserva entre redibujados. Es proporcionado por el motor central cuando es necesario. En los vnodes de componente POJO, el state hereda prototípicamente del objeto/clase del componente. En los vnodes de componente de clase, es una instancia de la clase. En los componentes de cierre, es el objeto devuelto por el cierre. |
events | Object? | Un objeto que se conserva entre redibujados y que almacena controladores de eventos para que puedan eliminarse utilizando la API del DOM. La propiedad events es undefined si no hay controladores de eventos definidos. Esta propiedad solo la usa internamente Mithril.js, no la use ni la modifique. |
instance | Object? | Para los componentes, una ubicación de almacenamiento para el valor devuelto por la view . Esta propiedad solo la usa internamente Mithril.js, no la use ni la modifique. |
Tipos de Vnode
La propiedad tag
de un vnode determina su tipo. Hay cinco tipos de vnode:
Tipo de Vnode | Ejemplo | Descripción |
---|---|---|
Elemento | {tag: "div"} | Representa un elemento DOM. |
Fragmento | {tag: "[", children: []} | Representa una lista de elementos DOM cuyo elemento DOM principal también puede contener otros elementos que no están en el fragmento. Cuando se utiliza la función auxiliar m() , los vnodes de fragmento solo se pueden crear anidando matrices en el parámetro children de m() . m("[") no crea un vnode válido. |
Texto | {tag: "#", children: ""} | Representa un nodo de texto DOM. |
HTML Confiable | {tag: "<", children: "<br>"} | Representa una lista de elementos DOM de una cadena HTML. |
Componente | {tag: ExampleComponent} | Si tag es un objeto de JavaScript con un método view , el vnode representa el DOM generado al renderizar el componente. |
Cada elemento en un árbol de DOM virtual es un vnode, incluido el texto. La utilidad m()
normaliza automáticamente su argumento children
y convierte las cadenas en vnodes de texto y las matrices anidadas en vnodes de fragmento.
Solo los nombres de etiqueta de elemento y los componentes pueden ser el primer argumento de la función m()
. En otras palabras, [
, #
y <
no son selectores válidos para m()
. Los vnodes de HTML confiable se pueden crear a través de m.trust()
Clase monomórfica
Mithril.js utiliza el módulo mithril/render/vnode
para generar todos los vnodes. Esto permite que los motores JavaScript modernos optimicen la diferenciación del DOM virtual compilando siempre los vnodes a la misma clase oculta.
Al desarrollar bibliotecas que generan vnodes, debe usar este módulo en lugar de escribir objetos de JavaScript desnudos para garantizar un alto nivel de rendimiento de renderizado.
Evitar anti-patrones
Evitar la reutilización de vnodes
Se supone que los vnodes representan el estado del DOM en un momento dado. El motor de renderizado de Mithril.js asume que un vnode reutilizado no ha cambiado, por lo que modificar un vnode utilizado en un renderizado anterior puede resultar en un comportamiento indefinido.
Se pueden reutilizar vnodes para evitar el diffing, pero es preferible usar onbeforeupdate
.
Evitar pasar datos del modelo directamente a los componentes a través de atributos
La propiedad key
podría entrar en conflicto con la lógica de Mithril.js en el modelo de datos, y el modelo podría ser una instancia mutable con un método que comparta un nombre con un hook del ciclo de vida como onupdate
o onremove
. Por ejemplo, un modelo podría usar una propiedad key
para representar una clave de color personalizable. Cuando esto ocurre, puede provocar que los componentes reciban datos incorrectos, cambien de posición inesperadamente o tengan otro comportamiento inesperado e indeseado. En su lugar, páselo como atributo para evitar interpretaciones incorrectas (y para que aún pueda mutarlo o llamar a métodos de prototipo más adelante):
// Modelo de datos
var users = [
{ id: 1, name: 'John', key: 'red' },
{ id: 2, name: 'Mary', key: 'blue' },
];
// Más tarde...
users[0].key = 'yellow';
// EVITAR
users.map(function (user) {
// El componente para John será destruido y recreado
return m(UserComponent, user);
});
// PREFERIR
users.map(function (user) {
// La clave se extrae específicamente: el modelo de datos recibe su propia propiedad
return m(UserComponent, { key: user.id, model: user });
});
Evitar declaraciones en los métodos de vista
Las sentencias de JavaScript en los métodos de vista a menudo requieren cambiar la estructura naturalmente anidada de un árbol HTML, lo que hace que el código sea más verboso y menos legible.
// EVITAR
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);
},
};
En su lugar, prefiera usar expresiones de JavaScript, como el operador ternario para el renderizado condicional y los métodos de array para estructuras similares a listas.
// PREFERIR
var BetterListComponent = {
view: function (vnode) {
return m(
'ul',
vnode.attrs.items.map(function (item) {
return m('li', item);
})
);
},
};