Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Let's build a Virtual DOM 🌈!

keyserfaty
November 25, 2017

Let's build a Virtual DOM 🌈!

keyserfaty

November 25, 2017
Tweet

More Decks by keyserfaty

Other Decks in Programming

Transcript

  1. // 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)
  2. // 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) } })
  3. // 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) } })
  4. // 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) } })
  5. // 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) } })
  6. // 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) } })
  7. // 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) } })
  8. // 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) } })
  9. Sería mejor si pudiéramos hacer todos los cambios en el

    DOM en grupo en vez de individualmente
  10. const container = { type: 'div', props: { class: 'container'

    }, children: [ // input & text-container ] }
  11. const element = ( h('div', { className: 'container' }, h('input',

    { type: 'text', onKeyUp: () => {} }), h('div', { className: 'text-container' }, ‘Un texto!’) ) )
  12. 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!’ ] } ] } )
  13. const createElement = node => { if (typeof node ===

    'string') { return document.createTextNode(node) } return document.createElement(node.type) }
  14. 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 }
  15. 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 }
  16. 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 }
  17. 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 }
  18. 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])) }
  19. 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 ) }
  20. 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 }
  21. Si queremos que nuestra app cambie cuando hay un cambio

    en el state no podemos hacer los cambios en el eventListener
  22. $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) } })
  23. Por qué mejor no armamos una función a la que

    le pasemos nuestro árbol de nodos
  24. $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) } })
  25. Pero va a ser genérica y va a funcionar para

    cualquier cambio que se produzca en el árbol de nodos
  26. Momento 0: No tenemos nodos en el DOM Momento 1:

    Hacemos el primer rendering de los nodos
  27. Á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: [] } ] }
  28. const updateElement = ($parent, newNode, oldNode) => { if (!oldNode)

    { $parent.appendChild(createElement(newNode)) } }
  29. const updateElement = ($parent, newNode, oldNode) => { if (!oldNode)

    { $parent.appendChild(createElement(newNode)) } }
  30. Momento 0: el div está vacío Momento 1: el div

    tiene un textElement adentro con el texto ‘TEXTO!’
  31. Á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: [] } ] }
  32. 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]) } }
  33. 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]) } }
  34. Á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: [] } ] }
  35. 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]) } }
  36. 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]) } }
  37. Esto nos va a servir para un elemento pero necesitamos

    que la función recorra todo el árbol
  38. 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) } } }
  39. 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) } } }
  40. Crea nodos en el vDOM => Crea los nodos en

    el DOM => (Cambios / eventos) => Aplica el diffing algorithm => Hace patching en el DOM
  41. Crea nodos en el vDOM => Crea los nodos en

    el DOM => (Cambios / eventos) => Aplica el diffing algorithm => Hace patching en el DOM
  42. Crea nodos en el vDOM => Crea los nodos en

    el DOM => (Cambios / eventos) => Aplica el diffing algorithm => Hace patching en el DOM
  43. Crea nodos en el vDOM => Crea los nodos en

    el DOM => (Cambios / eventos) => Aplica el diffing algorithm => Hace patching en el DOM
  44. Crea nodos en el vDOM => Crea los nodos en

    el DOM => (Cambios / eventos) => Aplica el diffing algorithm => Hace patching en el DOM
  45. Crea nodos en el vDOM => Crea los nodos en

    el DOM => (Cambios / eventos) => Aplica el diffing algorithm => Hace patching en el DOM
  46. Entonces: 1- Creamos un virtual DOM (`h`) 2- Creamos los

    elementos en el DOM (`createElement`) 3- Aplicamos los cambios necesarios (`updateElement`)