Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Let's build a Virtual DOM 🌈!
Search
keyserfaty
November 25, 2017
Programming
66
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Let's build a Virtual DOM 🌈!
keyserfaty
November 25, 2017
More Decks by keyserfaty
See All by keyserfaty
Cómo validar una idea en 8 horas y con (casi) $0
keyserfaty
0
54
Animaciones en Vanilla JS
keyserfaty
0
51
CSS & React - Problemas, ejemplos y soluciones
keyserfaty
0
120
How to (maybe) structure a React application
keyserfaty
2
280
Other Decks in Programming
See All in Programming
Agents on Rails - Rails at Scale 2026
irinanazarova
0
560
月間100万人のサービス刷新で、 技術選定をHonoに振り切った話
yutobara
8
6.6k
ソニーのクラウド共通基盤の変遷とAI時代の開発スタイルに合わせた進化 / The Journey of Sony’s Common Cloud Platform and Its Evolution for AI-Native Development
kenjiyoneyama
0
270
見えないものを探る要求要件定義に必要な基本的思考 / invisible-requirement-thinking
minodriven
15
8.5k
機械に任せるテスト、人が見るテスト ⽣成AIで引き直した、運⽤保守フェーズの線引き
dske104
0
190
Simple Storage Service(S3) is not simple
iwatsukayura
0
290
IBM Bob Dojo #1 仕様駆動開発入門
oniak3ibm
PRO
0
390
Apple Intelligence を用いた個人情報誤送信防止、及びユーザーリクエスト体験の改善について
yukiny
0
350
Event is what happens
moro
2
810
[Dev Group Paris Octobre 26] Agentforce, SIforce, Claudeforce, Slackbot… on s'y retrouve ?
nabondance
0
110
作って学ぶSpring BootのAuto Configuration / JJUG Night Seminar 2026-10-19
rshindo
0
150
APNsからLive Activityを開始する話
yumnumm
0
170
Featured
See All Featured
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
17k
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
3.2k
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
520
Visualization
eitanlees
153
17k
Believing is Seeing
oripsolob
1
240
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
2
330
Large-scale JavaScript Application Architecture
addyosmani
516
110k
Practical Orchestrator
shlominoach
192
12k
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
68
58k
Ruling the World: When Life Gets Gamed
codingconduct
0
400
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.9k
What's in a price? How to price your products and services
michaelherold
247
13k
Transcript
Let’s build a Virtual DOM! JSDayUY ~ 2017
Hola! Soy Karen " Developer en Wingu (@desdewingu) => Trabajando
en Nilus (@nilusorg) @keyserfaty
Alguna vez hicieron una app sin usar ningún framework? ✋
Y sin usar jQuery tampoco? ✋
Digamos que queremos hacer que un input cambie el texto
en un div
None
Cómo hacemos esto en vanilla JS?
// Crea los nodos const $input = document.createElement('input') $input.setAttribute('type', 'text')
$input.setAttribute('value', '') const $textContainer = document.createElement('div') $textContainer.setAttribute('class', 'text-container') const $container = document.createElement('div') $container.setAttribute('class', 'container') $container.appendChild($input) $container.appendChild($textContainer) const $root = document.querySelector('#root') $root.appendChild($container)
// Appendea eventos al input $input.addEventListener('keyup', function (e) { const
text = e.target.value const $textNode = document.createTextNode(text) const $oldTextNode = $textContainer.childNodes[0] if (!$oldTextNode) { $textContainer.appendChild($textNode) } else { $textContainer.replaceChild($textNode, $oldTextNode) } })
Qué acaba de pasar?
(the real DOM)
// Appendea eventos al input $input.addEventListener('keyup', function (e) { const
text = e.target.value const $textNode = document.createTextNode(text) const $oldTextNode = $textContainer.childNodes[0] if (!$oldTextNode) { $textContainer.appendChild($textNode) } else { $textContainer.replaceChild($textNode, $oldTextNode) } })
// Appendea eventos al input $input.addEventListener('keyup', function (e) { const
text = e.target.value const $textNode = document.createTextNode(text) const $oldTextNode = $textContainer.childNodes[0] if (!$oldTextNode) { $textContainer.appendChild($textNode) } else { $textContainer.replaceChild($textNode, $oldTextNode) } })
// Appendea eventos al input $input.addEventListener('keyup', function (e) { const
text = e.target.value const $textNode = document.createTextNode(text) const $oldTextNode = $textContainer.childNodes[0] if (!$oldTextNode) { $textContainer.appendChild($textNode) } else { $textContainer.replaceChild($textNode, $oldTextNode) } })
// Appendea eventos al input $input.addEventListener('keyup', function (e) { const
text = e.target.value const $textNode = document.createTextNode(text) const $oldTextNode = $textContainer.childNodes[0] if (!$oldTextNode) { $textContainer.appendChild($textNode) } else { $textContainer.replaceChild($textNode, $oldTextNode) } })
// Appendea eventos al input $input.addEventListener('keyup', function (e) { const
text = e.target.value const $textNode = document.createTextNode(text) const $oldTextNode = $textContainer.childNodes[0] if (!$oldTextNode) { $textContainer.appendChild($textNode) } else { $textContainer.replaceChild($textNode, $oldTextNode) } })
// Appendea eventos al input $input.addEventListener('keyup', function (e) { const
text = e.target.value const $textNode = document.createTextNode(text) const $oldTextNode = $textContainer.childNodes[0] if (!$oldTextNode) { $textContainer.appendChild($textNode) } else { $textContainer.replaceChild($textNode, $oldTextNode) } })
Qué está pasando por atrás mientras hacemos todo esto?
(WebKit)
Y qué problema tiene esto?
1. El proceso de repainting del browser es lento
2. Si tenemos 200 nodos podríamos tener potencialmente 200 re-renders
Sería mejor si pudiéramos hacer todos los cambios en el
DOM en grupo en vez de individualmente
Digamos que queremos hacer nuestra ““app”” de nuevo pero usando
la API del DOM lo menos posible
Podemos empezar por no crear todos los nodos al principio
Podemos probar creando una representación de esos nodos y después
crearlos todos juntos al final
const container = { type: 'div', props: { class: 'container'
}, children: [ // input & text-container ] }
Como crear estos objetos es una experiencia horrible podemos crear
una función que los cree por nosotros
const h = (type, props, ...children) => ({ type, props,
children })
const element = ( h('div', { className: 'container' }, h('input',
{ type: 'text', onKeyUp: () => {} }), h('div', { className: 'text-container' }, ‘Un texto!’) ) )
const element = ( h('div', { className: 'container' }, h('input',
{ type: 'text', onKeyUp: () => {} }), h('div', { className: 'text-container' }, ‘Un texto!’) ) ) => const element = ( { type: 'div', props: { className: 'container' }, children: [ { type: 'input', props: { type: 'text', onKeyUp: () => {} } }, { type: 'div', props: { className: 'text-container' }, children: [ ‘Un texto!’ ] } ] } )
1- Crear una representación de los nodos del DOM
Ahora necesitamos convertir a esos nodos en elementos del DOM
posta
const createElement = node => { if (typeof node ===
'string') { return document.createTextNode(node) } return document.createElement(node.type) }
None
const createElement = node => { if (typeof node ===
'string') { return document.createTextNode(node) } const $el = document.createElement(node.type) node.children .map(child => createElement(child)) .forEach(childNode => $el.appendChild(childNode)) return $el }
const createElement = node => { if (typeof node ===
'string') { return document.createTextNode(node) } const $el = document.createElement(node.type) node.children .map(child => createElement(child)) .forEach(childNode => $el.appendChild(childNode)) return $el }
Qué pasa con las props?
const createElement = node => { if (typeof node ===
'string') { return document.createTextNode(node) } const $el = document.createElement(node.type) setProps($el, node.props) setEvents($el, node.props) node.children .map(child => createElement(child)) .forEach(childNode => $el.appendChild(childNode)) return $el }
const createElement = node => { if (typeof node ===
'string') { return document.createTextNode(node) } const $el = document.createElement(node.type) setProps($el, node.props) setEvents($el, node.props) node.children .map(child => createElement(child)) .forEach(childNode => $el.appendChild(childNode)) return $el }
const setProp = ($el, name, value) => { if (isEventProp(name))
{ return } else if (name === 'className') { $el.setAttribute('class', value) } else { $el.setAttribute(name, value) } } const setProps = ($el, props) => { Object.keys(props) .forEach(prop => setProp($el, prop, props[prop])) }
const setEvent = ($el, name, value) => { $el.addEventListener(extractName(name), value)
} const setEvents = ($el, props) => { Object.keys(props) .forEach(prop => isEventProp(prop) ? setEvent($el, prop, props[prop]) : null ) }
const createElement = node => { if (typeof node ===
'string') { return document.createTextNode(node) } const $el = document.createElement(node.type) setProps($el, node.props) setEvents($el, node.props) node.children .map(child => createElement(child)) .forEach(childNode => $el.appendChild(childNode)) return $el }
const $root = document.getElementById('root') $root.appendChild(createElement(element))
2- Crear todos los nodos juntos y appendearlos al DOM
Antes de seguir vamos a hacer una suposición:
Supongamos que estamos guardando nuestro state de alguna forma (con
Redux, por ejemplo)
Si queremos que nuestra app cambie cuando hay un cambio
en el state no podemos hacer los cambios en el eventListener
$input.addEventListener('keyup', function (e) { const text = e.target.value const $textNode
= document.createTextNode(text) const $oldTextNode = $textContainer.childNodes[0] if (!$oldTextNode) { $textContainer.appendChild($textNode) } else { $textContainer.replaceChild($textNode, $oldTextNode) } })
Tenemos que hacer algo más declarativo y menos imperativo
Por qué mejor no armamos una función a la que
le pasemos nuestro árbol de nodos
Y que haga los cambios que necesita comparándolo con el
árbol anterior
Esta función va a hacer algo parecido a esto:
$input.addEventListener('keyup', function (e) { const text = e.target.value const $textNode
= document.createTextNode(text) const $oldTextNode = $textContainer.childNodes[0] if (!$oldTextNode) { $textContainer.appendChild($textNode) } else { $textContainer.replaceChild($textNode, $oldTextNode) } })
Pero va a ser genérica y va a funcionar para
cualquier cambio que se produzca en el árbol de nodos
Tenemos nuestros dos árboles de nodos:
Momento 0: No tenemos nodos en el DOM Momento 1:
Hacemos el primer rendering de los nodos
Árbol 1: Árbol 2: (nada) { type: 'div', props: {
className: 'container' }, children: [ { type: 'input', props: { type: 'text', onKeyUp: updateText } }, { type: 'div', props: { className: 'text-container' }, children: [] } ] }
CASO 1: Hay que renderizar el nodo en el DOM
const updateElement = ($parent, newNode, oldNode) => { if (!oldNode)
{ $parent.appendChild(createElement(newNode)) } }
const updateElement = ($parent, newNode, oldNode) => { if (!oldNode)
{ $parent.appendChild(createElement(newNode)) } }
Momento 0: el div está vacío Momento 1: el div
tiene un textElement adentro con el texto ‘TEXTO!’
Árbol 1: Árbol 2: { type: 'div', props: { className:
'container' }, children: [ { type: 'input', props: { type: 'text', onKeyUp: updateText } }, { type: 'div', props: { className: 'text-container' }, children: [‘TEXTO!’] } ] } { type: 'div', props: { className: 'container' }, children: [ { type: 'input', props: { type: 'text', onKeyUp: updateText } }, { type: 'div', props: { className: 'text-container' }, children: [] } ] }
CASO 2: El contenido de uno de los children cambió
y hay que actualizarlo
const updateElement = ($parent, newNode, oldNode, index = 0) =>
{ if (!oldNode) { $parent.appendChild(createElement(newNode)) } else if (nodeChanged(newNode, oldNode)) { $parent.replaceChild(createElement(newNode), $parent.childNodes[index]) } }
const updateElement = ($parent, newNode, oldNode, index = 0) =>
{ if (!oldNode) { $parent.appendChild(createElement(newNode)) } else if (nodeChanged(newNode, oldNode)) { $parent.replaceChild(createElement(newNode), $parent.childNodes[index]) } }
Momento 0: el div tiene contenido Momento 1: el div
está vacío
Árbol 1: Árbol 2: { type: 'div', props: { className:
'container' }, children: [ { type: 'input', props: { type: 'text', onKeyUp: updateText } }, { type: 'div', props: { className: 'text-container' }, children: [‘TEXTO!’] } ] } { type: 'div', props: { className: 'container' }, children: [ { type: 'input', props: { type: 'text', onKeyUp: updateText } }, { type: 'div', props: { className: 'text-container' }, children: [] } ] }
CASO 3: Uno de los children tenía un nodo que
ya no tiene más
const updateElement = ($parent, newNode, oldNode, index = 0) =>
{ if (!oldNode) { $parent.appendChild(createElement(newNode)) } else if (!newNode) { $parent.removeChild($parent.childNodes[index]) } else if (nodeChanged(newNode, oldNode)) { $parent.replaceChild(createElement(newNode), $parent.childNodes[index]) } }
const updateElement = ($parent, newNode, oldNode, index = 0) =>
{ if (!oldNode) { $parent.appendChild(createElement(newNode)) } else if (!newNode) { $parent.removeChild($parent.childNodes[index]) } else if (nodeChanged(newNode, oldNode)) { $parent.replaceChild(createElement(newNode), $parent.childNodes[index]) } }
Esto nos va a servir para un elemento pero necesitamos
que la función recorra todo el árbol
La función completa se ve así:
const updateElement = ($parent, newNode, oldNode, index = 0) =>
{ if (!oldNode) { $parent.appendChild(createElement(newNode)) } else if (!newNode) { $parent.removeChild($parent.childNodes[index]) } else if (nodeChanged(newNode, oldNode)) { $parent.replaceChild(createElement(newNode), $parent.childNodes[index]) } else if (newNode.type) { const newLength = newNode.children.length const oldLength = oldNode.childNodes.length for (let i = 0; i < newLength || i < oldLength; i++) { updateElement($parent.childNodes[index], newNode.children[i], oldNode.children[i], i) } } }
const updateElement = ($parent, newNode, oldNode, index = 0) =>
{ if (!oldNode) { $parent.appendChild(createElement(newNode)) } else if (!newNode) { $parent.removeChild($parent.childNodes[index]) } else if (nodeChanged(newNode, oldNode)) { $parent.replaceChild(createElement(newNode), $parent.childNodes[index]) } else if (newNode.type) { const newLength = newNode.children.length const oldLength = oldNode.childNodes.length for (let i = 0; i < newLength || i < oldLength; i++) { updateElement($parent.childNodes[index], newNode.children[i], oldNode.children[i], i) } } }
3- Hacer cambios en el árbol de nodos cuando se
produzca algún evento
Hasta ahora no mencionamos al virtual DOM
Ni dijimos diffing algorithm
Ni blockchain
None
Recapitulando: Nuestra función updateElement es nuestro diffing algorithm
Compara dos árboles de nodos (del virtual DOM) y actualiza
solo las partes que cambiaron
Internamente React (y otros) tienen un proceso que es más
o menos así:
Crea nodos en el vDOM => Crea los nodos en
el DOM => (Cambios / eventos) => Aplica el diffing algorithm => Hace patching en el DOM
Crea nodos en el vDOM => Crea los nodos en
el DOM => (Cambios / eventos) => Aplica el diffing algorithm => Hace patching en el DOM
Crea nodos en el vDOM => Crea los nodos en
el DOM => (Cambios / eventos) => Aplica el diffing algorithm => Hace patching en el DOM
Crea nodos en el vDOM => Crea los nodos en
el DOM => (Cambios / eventos) => Aplica el diffing algorithm => Hace patching en el DOM
Crea nodos en el vDOM => Crea los nodos en
el DOM => (Cambios / eventos) => Aplica el diffing algorithm => Hace patching en el DOM
Crea nodos en el vDOM => Crea los nodos en
el DOM => (Cambios / eventos) => Aplica el diffing algorithm => Hace patching en el DOM
None
Entonces: 1- Creamos un virtual DOM (`h`) 2- Creamos los
elementos en el DOM (`createElement`) 3- Aplicamos los cambios necesarios (`updateElement`)
Muy interesante todo Karen. A dónde puedo ir a leer
más sobre esto?
krn.sh/jsdayuy => slides / repo / links
Gracias! @keyserfaty