Tu navegador no soporta las features requeridas por impress.js.

Para la mejor experiencia usá Chrome, Safari o Firefox.

Desmitificando el Virtual DOM

Cómo funciona por dentro

Ron Bachmann

BeerJS Córdoba

- [Fondo minimalista oscuro con código o abstracto] - [Animación: fade in suave del título] - Sonrisa, mirar a la audiencia

Hola, soy Ron Bachmann

Estudio en FAMAF (UNC)

Experiencia

Expty GmbH (Dec 2025 - Presente)
Morphais (Feb 2024 - Oct 2025)
- Presentación breve - Mencionar experiencia relevante

El verdadero objetivo de hoy

Entender cómo funciona la herramienta que ya usan

y deje de ser una "caja negra mágica"

?
- [Caja negra visual → se abre lentamente revelando el interior] - [Transición suave al problema real]

Antes de empezar...

Días desde que salió el último framework de JavaScript:

84

En promedio, sale uno nuevo cada

91

días

- [Contador grande en monospace centrado] - [Pause para risas - tono irónico] - Delivery: seco, medio sarcástico

El DOM es lento

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

Reflow

El navegador recalcula el layout

Cambiar width, height, agregar elementos

Repaint

El navegador vuelve a dibujar

Cambiar color, background, visibility

- [Animación mostrando el árbol DOM siendo reconstruido] - Explicar el problema del ejemplo

La analogía del chef

DOM Directo:

Chef recibe pedido → Tira toda la comida → Cocina todo de nuevo

Con VDOM:

Chef compara pedidos → Solo cambia lo diferente

- **Transición:** "¿Cómo resolvemos esto?"

Virtual DOM

Una copia del DOM... pero en memoria (JavaScript puro)

Real DOM

<div>
  <h1>Hola</h1>
  <p>Mundo</p>
</div>

Virtual DOM

{
  tag: 'div',
  props: {},
  children: [
   {
     tag: 'h1',
     props: {},
     children: 'Hola'
   },
   {
     tag: 'p',
     props: {},
     children: 'Mundo'
   }
  ]
}
- [Diagrama de dos árboles lado a lado] - [Flecha animada mostrando "diff" entre ambos] - Enfatizar: "Es solo un objeto JavaScript"

Paso 1: Creando nodos virtuales

La función h()

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
}

Uso práctico

const vnode = h('div', { class: 'container' }, [
  h('h1', {}, 'Hola Mundo'),
  h('p', {}, 'Esto es VDOM')
])
- [Código resaltado línea por línea] - [Animación: el código se transforma visualmente en un árbol] - [Mostrar el objeto resultante] - [Visual del árbol generado]

Paso 2: Del virtual al real

La función mount()

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);
}
- [Visual del árbol siendo "construido" nodo por nodo] - [Animación mostrando la recursión paso a paso] - [Números 1-4 apareciendo secuencialmente junto al código]

Paso 3: Reconciliación - CASO 1

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
}
- Si el tag cambió, no tiene sentido comparar - directamente reemplazamos

Paso 3: Reconciliación - CASO 2

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
}
- Si es texto simple, solo actualizamos el contenido

Paso 3: Reconciliación - CASO 3

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));
  }
}
- Ver slide siguiente para explicacion visual

Paso 3: CASO 3 - Visual claro

Escenario ejemplo: newChildren > oldChildren (agregar extras)

- Slide dedicada para explicar CASO 3 con menos ruido visual - Mostrar un solo escenario activo para mejorar claridad

Demo

Métricas: -
- [Panel dividido: código a la izquierda, resultado a la derecha] - [Inspector del DOM mostrando solo los nodos que cambian - resaltados en verde/rojo] - [Animación lenta del patch() para que se vea qué hace]

Benchmark: VDOM vs DOM

Runs: 0

DOM directo

Esperando ejecución...

VDOM

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) - - - - -
- Benchmark real en vivo, sin números hardcodeados - Mostrar el estado final en ambos paneles

Cuándo usar (y no usar) VDOM

Cuándo más importa

  • Listas grandes que cambian frecuentemente

    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

  • Updates rápidos (cada 16ms = 60fps)

    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

  • Aplicaciones complejas con mucho estado

    La inmutabilidad del VDOM simplifica el tracking de cambios

    Dashboards administrativos, editores WYSIWYG, IDEs web

Cuándo es overkill

  • Apps pequeñas

    30KB+ de runtime solo para VDOM

    Alternativa: Vanilla JS o petite-vue (2KB)

    Todo list simple, calculadora

  • Landing pages

    Hydration innecesaria, TTI aumentado

    Alternativa: HTML estático + Alpine.js

    Páginas de marketing, portfolios

  • Apps con updates poco frecuentes

    El overhead del diff no se amortiza

    Alternativa: jQuery moderno o vanilla con delegación manual

    Blogs, documentación, e-commerce simple

Alternativas modernas

  • Svelte: Compila a código vanilla, sin runtime de VDOM
  • SolidJS: Signals + re-renders selectivos sin diff completo
  • Vue 3: Compiler optimiza antes del runtime
- "A veces, menos abstracción = más performance"

Optimizaciones del Virtual DOM

Keys en listas

Identificadores únicos que permiten reutilizar elementos del DOM existentes en lugar de recrearlos.

Mejora "Reordenar nodos" y "Updates parciales"

Event Delegation

Un solo listener en el contenedor padre maneja eventos de todos los elementos hijos.

Reduce tiempo en "Crear nodos" y "Append nodos"

Batch Updates

Agrupa múltiples cambios de estado en una sola pasada de reconciliación.

Optimiza "Cambios parciales" y "Actualizar textos"

Memoization

Evita re-renderizar componentes cuando sus props y estado no han cambiado.

Beneficia "Actualizar textos" y "Cambios parciales"

Diff por componentes

Compara solo los subárboles de componentes que reportaron cambios.

Mejora performance en todos los test cases

Modo concurrente

Interrumpe renderizados largos para priorizar tareas más importantes.

Beneficia "Cambios parciales" y mantiene UI responsiva

Recursos

  • Código completo: github.com/Lautron/vdom-beerjs
QR Code GitHub

TL;DR

  • El DOM es lento porque cada cambio causa reflows y repaints
  • Virtual DOM = representación en memoria del DOM usando objetos JS puros
  • La magia está en el algoritmo de reconciliación que compara y actualiza solo lo necesario
  • Construimos un VDOM básico en vivo con TypeScript: función h(), mount() y patch()
  • Benchmarks comparando nuestra implementación simple vs DOM directo
  • VDOM brilla en apps complejas con updates frecuentes, pero es overkill para apps simples
  • Frameworks modernos agregan optimizaciones como keys, event delegation, batch updates y memoization
  • Entender cómo funciona internamente te hace mejor desarrollador, incluso si nunca lo implementás manualmente

Overview - Presiona ESC para ver