Saltar al contenido

Sin virtual DOM ni re-render

Cómo Solid parte del JSX y actualiza el DOM directamente: el compilador que convierte tu markup en plantillas y efectos, frente a la reconciliación de árboles virtuales.

Dos formas de llegar al DOM

Todo framework de UI resuelve el mismo problema: mantener el DOM en sincronía con el estado. Hay dos estrategias de fondo, y definen todo lo demás.

La ruta del virtual DOM trata el JSX como datos: en cada render construye una descripción nueva de la UI y la compara con la anterior para deducir qué tocar. La ruta de Solid trata el JSX como código: un compilador lo convierte, antes de que el navegador lo vea, en una plantilla estática más un puñado de efectos que actualizan las partes que cambian.

La distinción “datos contra código” no es retórica: decide quién trabaja y cuándo. Tratar el JSX como datos aplaza toda la decisión al runtime, que debe recalcular la descripción y compararla en cada cambio. Tratarlo como código adelanta esa decisión al compilador, que emite instrucciones específicas para cada parte dinámica. Es la diferencia entre interpretar una partitura en directo cada vez y grabarla una sola vez en un rollo de pianola.

Qué compila Solid: del JSX a instrucciones

Este es el corazón. Tomemos un fragmento y veamos su forma compilada, de manera conceptual.

JSX
// Lo que escribesfunction Saludo(props) {  return <div class={props.tema}>Hola, {props.nombre()}</div>;}
JS
// En lo que compila Solid, de forma simplificadaconst _tmpl = template(`<div>Hola, </div>`);function Saludo(props) {  const el = _tmpl();            // clona un nodo real, una vez  insert(el, props.nombre);     // efecto: mantiene el texto en sync  effect(() => setAttribute(el, "class", props.tema));  // efecto para la clase  return el;                    // devuelve un nodo del DOM, no un objeto describiendolo}

Fíjate en tres cosas. Primero, la parte estática (<div>Hola, ) se convierte en una plantilla que se clona con cloneNode, la operación más barata que ofrece el navegador. Segundo, cada parte dinámica (props.nombre() y props.tema) genera su propio efecto de grano fino, ligado a un nodo o atributo concreto. Tercero, el componente devuelve un nodo real del DOM, no un árbol virtual que alguien tenga que interpretar después.

La consecuencia es que el tamaño de tu componente no impone un coste de runtime recurrente. Cien nodos estáticos son una plantilla que se clona una vez; solo las partes que leen un signal generan efectos. La parte inmutable de tu interfaz es, literalmente, gratis tras el primer montaje, mientras que en un modelo de virtual DOM cada uno de esos cien nodos se re-describe y se re-compara en cada render aunque nunca cambie.

Reconciliación: el trabajo que Solid no hace

La reconciliación es el algoritmo que, dado un árbol virtual nuevo y uno viejo, calcula el conjunto mínimo de mutaciones del DOM. Es una idea brillante y costosa: exige recrear la descripción de la UI en cada cambio y recorrerla entera para compararla, aunque solo haya cambiado un carácter. Las claves de lista, la memoización de componentes y la comparación de props existen, en buena medida, para abaratar ese recorrido.

Solid salta los dos primeros pasos por completo. No hay árbol que reconstruir porque nunca hubo un árbol virtual; no hay comparación porque el compilador ya asoció cada dato con su nodo. Solo queda la mutación puntual. Su análogo de la reconciliación, para listas, es <For>, que reordena nodos reales por referencia sin diffear estructura.

JSX
import { For, createSignal } from "solid-js";function Lista() {  const [items] = createSignal([{ id: 1, txt: "a" }, { id: 2, txt: "b" }]);  return (    <ul>      <For each={items()}>{(item) => <li>{item.txt}</li>}</For>    </ul>  );}

<For> es keyed por la referencia del objeto: si reordenas el array, Solid mueve los nodos <li> existentes en lugar de recrearlos, y solo crea o destruye los que de verdad aparecen o desaparecen. No hay un algoritmo de diff de árbol por detrás; hay un mapeo estable de cada elemento del array a su nodo real, que es justo lo que las claves de lista intentan aproximar en un modelo virtual.

El coste que desaparece

Sin virtual DOM caen varias cosas a la vez: el peso del reconciliador en el bundle, la asignación de memoria de los árboles virtuales que el recolector luego limpia, y el trabajo de CPU proporcional al tamaño de la UI en cada cambio. Lo que queda es trabajo proporcional a lo que de verdad cambió. En los benchmarks de referencia, Solid se sitúa consistentemente a un pelo del DOM manual, y esa cercanía no es casualidad: hace casi lo mismo que escribirías tú a mano.

En conjunto, prescindir del virtual DOM no es una microoptimización: reordena de dónde sale el coste. El navegador del usuario recibe una máquina de estado ya resuelta y se limita a ejecutar mutaciones puntuales; el trabajo de deducir qué cambió se pagó una sola vez, en tu build, en tu máquina. Ese es el motivo mecánico —no una cuestión de fe ni de marketing— por el que Solid rinde como rinde.

Nada de esto significa que el virtual DOM fuera un error. Fue una idea brillante que resolvió el problema de su época: hacer manejable la actualización del DOM cuando escribirla a mano era tedioso y propenso a fallos. Durante más de una década fue el mejor compromiso disponible entre ergonomía y rendimiento.

Solid se apoya en esa herencia y da el siguiente paso: conserva la comodidad de describir la interfaz con JSX, pero paga el coste del análisis en compilación en lugar de en cada frame del navegador. Es evolución, no rechazo — la misma meta de siempre, alcanzada moviendo el trabajo a un momento en que no lo sufre el usuario.