Articles

bitsofcode

Recientemente he estado escribiendo sobre lo que son exactamente el DOM y el shadow DOM y en qué se diferencian. Para recapitular, el Modelo de Objetos del Documento es una representación basada en objetos de un documento HTML y una interfaz para manipular ese objeto. El shadow DOM puede ser considerado como una versión «lite» del DOM. También es una representación basada en objetos de elementos HTML, pero no de un documento independiente completo. En su lugar, el shadow DOM nos permite separar nuestro DOM en trozos más pequeños y encapsulados que se pueden utilizar en todos los documentos HTML.

Otro término similar que puede haber encontrado es el «DOM virtual». Aunque el concepto ha existido durante varios años, se hizo más popular por su uso en el marco de React. En este artículo, cubriré exactamente qué es el DOM virtual, en qué se diferencia del DOM original y cómo se utiliza.

¿Por qué necesitamos un DOM virtual?

Para entender por qué surgió el concepto del DOM virtual, revisemos el DOM original. Como he mencionado, hay dos partes en el DOM – la representación basada en objetos del documento HTML y la API para manipular ese objeto.

Por ejemplo, tomemos este simple documento HTML con una lista desordenada y un elemento de lista.

<!doctype html><html lang="en"> <head></head> <body> <ul class="list"> <li class="list__item">List item</li> </ul> </body></html>

Este documento puede representarse como el siguiente árbol DOM:

  • html
    • head lang=»es»
    • body
      • ul class=»list»
        • li class=»list__item»
          • «List item»

Digamos que queremos modificar el contenido del primer elemento de la lista a "List item one" y además añadir un segundo elemento de la lista. Para ello, necesitaremos utilizar las APIs del DOM para encontrar los elementos que queremos actualizar, crear los nuevos elementos, añadir atributos y contenido, y finalmente actualizar los propios elementos del DOM.

const listItemOne = document.getElementsByClassName("list__item");listItemOne.textContent = "List item one";const list = document.getElementsByClassName("list");const listItemTwo = document.createElement("li");listItemTwo.classList.add("list__item");listItemTwo.textContent = "List item two";list.appendChild(listItemTwo);

El DOM no se hizo para esto…

Cuando se publicó la primera especificación para el DOM en 1998, construíamos y gestionábamos las páginas web de forma muy diferente. Había mucha menos confianza en las APIs del DOM para crear y actualizar el contenido de la página tan frecuentemente como lo hacemos hoy.

Métodos simples como document.getElementsByClassName() están bien para usar a pequeña escala, pero si estamos actualizando múltiples elementos en una página cada pocos segundos, puede empezar a ser realmente caro consultar y actualizar constantemente el DOM.

Además, debido a la forma en que están configuradas las APIs, suele ser más sencillo realizar operaciones más costosas en las que actualizamos partes más grandes del documento que encontrar y actualizar los elementos específicos. Volviendo a nuestro ejemplo de la lista, en cierto modo es más fácil reemplazar toda la lista desordenada por una nueva que modificar los elementos específicos.

const list = document.getElementsByClassName("list");list.innerHTML = `<li class="list__item">List item one</li><li class="list__item">List item two</li>`;

En este ejemplo concreto, la diferencia de rendimiento entre los métodos es probablemente insignificante. Sin embargo, a medida que el tamaño de la página web crece, se vuelve más importante seleccionar y actualizar sólo lo que se necesita.

… ¡pero el DOM virtual lo era!

El DOM virtual fue creado para resolver estos problemas de necesidad de actualizar frecuentemente el DOM de una manera más performante. A diferencia del DOM o del shadow DOM, el DOM virtual no es una especificación oficial, sino un nuevo método de interfaz con el DOM.

Un DOM virtual puede considerarse como una copia del DOM original. Esta copia puede ser frecuentemente manipulada y actualizada, sin utilizar las APIs del DOM. Una vez que todas las actualizaciones se han hecho en el DOM virtual, podemos ver qué cambios específicos hay que hacer en el DOM original y hacerlos de una manera dirigida y optimizada.

¿Qué aspecto tiene un DOM virtual?

El nombre «DOM virtual» tiende a aumentar el misterio de lo que realmente es el concepto. De hecho, un DOM virtual es sólo un objeto normal de Javascript.

Volvamos a ver el árbol DOM que creamos antes:

  • html
    • head lang=»es»
    • body
      • ul class=»list»
        • li class=»list__item»
          • «Elemento de la lista»

Este árbol también se puede representar como un objeto Javascript.

const vdom = { tagName: "html", children: } // end ul ] } // end body ]} // end html

Podemos pensar en este objeto como nuestro DOM virtual. Como el DOM original, es una representación basada en objetos de nuestro documento HTML. Pero como es un objeto Javascript plano, podemos manipularlo libremente y con frecuencia sin tocar el DOM real hasta que lo necesitemos.

En lugar de usar un objeto para todo el objeto, es más común trabajar con pequeñas secciones del DOM virtual. Por ejemplo, podemos trabajar en un componente list, que respondería a nuestro elemento de lista desordenada.

const list = { tagName: "ul", attributes: { "class": "list" }, children: };

Dentro del capó del DOM virtual

Ahora que hemos visto cómo es un DOM virtual, ¿cómo funciona para resolver los problemas de rendimiento y usabilidad del DOM?

Como ya he mencionado, podemos utilizar el DOM virtual para señalar los cambios específicos que hay que hacer en el DOM y hacer solo esas actualizaciones específicas. Volvamos a nuestro ejemplo de la lista desordenada y hagamos los mismos cambios que hicimos usando la API del DOM.

Lo primero que haríamos es hacer una copia del DOM virtual, que contenga los cambios que queremos hacer. Ya que no necesitamos utilizar las APIs del DOM, podemos crear un nuevo objeto.

const copy = { tagName: "ul", attributes: { "class": "list" }, children: };

Este copyse utiliza para crear lo que se llama un «diff» entre el DOM virtual original, en este caso el list, y el actualizado. Un diff podría ser algo así:

const diffs = 

Este diff proporciona instrucciones sobre cómo actualizar el DOM real. Una vez recogidos todos los diffs, podemos hacer cambios por lotes en el DOM, haciendo sólo las actualizaciones que sean necesarias.

Por ejemplo, podríamos hacer un bucle a través de cada diff y añadir un nuevo hijo o actualizar uno antiguo dependiendo de lo que especifique el diff.

const domElement = document.getElementsByClassName("list");diffs.forEach((diff) => { const newElement = document.createElement(diff.newNode.tagName); /* Add attributes ... */ if (diff.oldNode) { // If there is an old version, replace it with the new version domElement.replaceChild(diff.newNode, diff.index); } else { // If no old version exists, create a new node domElement.appendChild(diff.newNode); }})

Nótese que ésta es una versión realmente simplificada y despojada de cómo podría funcionar un DOM virtual y hay muchos casos que no he cubierto aquí.

El DOM virtual y los frameworks

Es más común trabajar con el DOM virtual a través de un framework, en lugar de interactuar con él directamente como mostré en el ejemplo anterior.

Frameworks como React y Vue utilizan el concepto de DOM virtual para hacer actualizaciones más performantes al DOM. Por ejemplo, nuestro componente list se puede escribir en React de la siguiente manera.

import React from 'react';import ReactDOM from 'react-dom';const list = React.createElement("ul", { className: "list" }, React.createElement("li", { className: "list__item" }, "List item"));ReactDOM.render(list, document.body);

Si quisiéramos actualizar nuestra lista, podríamos simplemente reescribir toda la plantilla de la lista, y llamar a ReactDOM.render() de nuevo, pasando la nueva lista.

const newList = React.createElement("ul", { className: "list" }, React.createElement("li", { className: "list__item" }, "List item one"), React.createElement("li", { className: "list__item" }, "List item two"););setTimeout(() => ReactDOM.render(newList, document.body), 5000);

Debido a que React utiliza el DOM virtual, aunque estemos re-renderizando toda la plantilla, sólo se actualizan las partes que realmente cambian. Si miramos en nuestras herramientas de desarrollo cuando se produce el cambio, veremos los elementos específicos y las partes específicas de los elementos que cambian.

El DOM vs el DOM virtual

Para recapitular, el DOM virtual es una herramienta que nos permite interactuar con los elementos del DOM de una manera más fácil y con mayor rendimiento. Se trata de una representación en forma de objeto de Javascript del DOM, que podemos modificar con la frecuencia que necesitemos. Los cambios realizados en este objeto se cotejan, y las modificaciones en el DOM real se orientan y se realizan con menos frecuencia.