Entender cómo funciona la herramienta que ya usan
y deje de ser una "caja negra mágica"
Días desde que salió el último framework de JavaScript:
84
En promedio, sale uno nuevo cada
91
días
Cada manipulación = Reflow + Repaint
// Esto es caro:
for(let i = 0; i < 1000; i++) {
element.innerHTML += `<div>${i}</div>`;
}
// Resultado: 1000 reflows, 1000 repaints
El navegador recalcula el layout
Cambiar width, height, agregar elementos
El navegador vuelve a dibujar
Cambiar color, background, visibility
Chef recibe pedido → Tira toda la comida → Cocina todo de nuevo
Chef compara pedidos → Solo cambia lo diferente
Una copia del DOM... pero en memoria (JavaScript puro)
<div>
<h1>Hola</h1>
<p>Mundo</p>
</div>
{
tag: 'div',
props: {},
children: [
{
tag: 'h1',
props: {},
children: 'Hola'
},
{
tag: 'p',
props: {},
children: 'Mundo'
}
]
}
export function h(
tag: string,
props: Record<string, any>,
children: string | VirtElem[]
): VirtElem {
return { tag, props, children }
}
interface VirtElem {
tag: string
props: Record<string, any>
children: string | VirtElem[]
domElem?: Element
}
const vnode = h('div', { class: 'container' }, [
h('h1', {}, 'Hola Mundo'),
h('p', {}, 'Esto es VDOM')
])
export function mount(virtElem: VirtElem, container: Element) {
// 1. Crear elemento DOM
const elem = document.createElement(virtElem.tag);
virtElem.domElem = elem;
// 2. Aplicar props como atributos
Object.entries(virtElem.props).forEach(([key, value]) => {
elem.setAttribute(key, value);
});
// 3. Manejar children recursivamente
if (typeof virtElem.children === 'string') {
elem.textContent = virtElem.children;
} else {
virtElem.children.forEach(child => mount(child, elem));
}
// 4. Insertar en el DOM
container.appendChild(elem);
}
Tags diferentes → Reemplazar todo
export function patch(oldElem: VirtElem, newElem: VirtElem) {
const domElem = (newElem.domElem = oldElem.domElem);
if (!domElem) return;
// CASO 1: Tags diferentes → reemplazar todo
if (oldElem.tag !== newElem.tag) {
mount(newElem, domElem.parentNode as Element);
unmount(oldElem);
return;
}
// ...continúa en siguiente slide
}
Nuevo children es texto → Actualizar textContent
export function patch(oldElem: VirtElem, newElem: VirtElem) {
// ...código del CASO 1...
// CASO 2: Nuevo children es texto
if (typeof newElem.children === 'string') {
domElem.textContent = newElem.children;
return;
}
// ...continúa en siguiente slide
}
Arrays de children → Comparar recursivamente
export function patch(oldElem: VirtElem, newElem: VirtElem) {
// ...código de casos anteriores...
// CASO 3: Arrays de children → comparar recursivamente
const oldChildren = oldElem.children as VirtElem[];
const newChildren = newElem.children as VirtElem[];
const minLength = Math.min(oldChildren.length, newChildren.length);
for (let i = 0; i < minLength; i++) {
patch(oldChildren[i], newChildren[i]);
}
if (oldChildren.length > newChildren.length) {
oldChildren.slice(minLength).forEach(unmount);
}
if (newChildren.length > oldChildren.length) {
newChildren.slice(minLength).forEach(child => mount(child, domElem));
}
}
Escenario ejemplo: newChildren > oldChildren (agregar extras)
Esperando ejecución...
Esperando ejecución...
| Operación | DOM (último) | VDOM (último) | DOM (prom) | VDOM (prom) | Mejora prom |
|---|---|---|---|---|---|
| Crear 80000 nodos | - | - | - | - | - |
| Actualizar 80000 textos (todos) | - | - | - | - | - |
| 50 cambios parciales (5000 c/u) | - | - | - | - | - |
| Append 40000 nodos | - | - | - | - | - |
| Eliminar 30000 nodos | - | - | - | - | - |
| Reordenar 10000 nodos (reverse) | - | - | - | - | - |
El diff evita comparaciones O(n²) del DOM real
Feed de Twitter, tabla de trading, chat en tiempo real
Con 1000 items: hasta 10-50x más rápido en updates parciales
Batch updates agrupan cambios en un solo reflow
Gráficos animados, drag & drop, juegos web
Sin VDOM: 60 reflows/segundo | Con VDOM: 1 reflow optimizado
La inmutabilidad del VDOM simplifica el tracking de cambios
Dashboards administrativos, editores WYSIWYG, IDEs web
30KB+ de runtime solo para VDOM
Alternativa: Vanilla JS o petite-vue (2KB)
Todo list simple, calculadora
Hydration innecesaria, TTI aumentado
Alternativa: HTML estático + Alpine.js
Páginas de marketing, portfolios
El overhead del diff no se amortiza
Alternativa: jQuery moderno o vanilla con delegación manual
Blogs, documentación, e-commerce simple
Identificadores únicos que permiten reutilizar elementos del DOM existentes en lugar de recrearlos.
Mejora "Reordenar nodos" y "Updates parciales"
Un solo listener en el contenedor padre maneja eventos de todos los elementos hijos.
Reduce tiempo en "Crear nodos" y "Append nodos"
Agrupa múltiples cambios de estado en una sola pasada de reconciliación.
Optimiza "Cambios parciales" y "Actualizar textos"
Evita re-renderizar componentes cuando sus props y estado no han cambiado.
Beneficia "Actualizar textos" y "Cambios parciales"
Compara solo los subárboles de componentes que reportaron cambios.
Mejora performance en todos los test cases
Interrumpe renderizados largos para priorizar tareas más importantes.
Beneficia "Cambios parciales" y mantiene UI responsiva
Overview - Presiona ESC para ver