What the v...DOM?

What the v...DOM?

22725c2d3eb331146549bf0d5d3c050c?s=128

stefan judis

April 18, 2018
Tweet

Transcript

  1. 3.
  2. 10.

    Go out of the door and turn left.
 Get on

    Frankfurter Allee until you get to the Proskauer Straße.
 Go left at the park. Go to the end of the park.
 My house is #22.
  3. 14.

    “ The declarative programming paradigm expresses the logic of a

    computation without describing its control flow.
  4. 15.

    Imperative function double (arr) { let results = []; for

    (let i = 0; i < arr.length; i++){ results.push(arr[i] * 2); } return results; } Declarative function double (arr) { return arr.map((item) => item * 2); }
  5. 17.

    const $btn = document.getElementById('btn'); $btn.addEventListener('click', _ => { $btn.classList.toggle('is-highlighted'); $btn.innerText

    = $btn.innerText === 'Highlight me!' ? 'Please stop!' : 'Highlight me!'; }); Imperative UI
  6. 20.

    export default class App extends Component { ... render(props, {

    highlighted }) { return ( <button type="button" highlighted={highlighted} onclick={this.toggleHighlight}> { highlighted ? 'Please stop!': 'Highlight me!' } </button> ); } } codesandbox.io/s/rwjol2n3op
  7. 21.

    export default class App extends Component { ... render(props, {

    highlighted }) { return ( <button type="button" highlighted={highlighted} onclick={this.toggleHighlight}> { highlighted ? 'Please stop!': 'Highlight me!' } </button> ); } } codesandbox.io/s/rwjol2n3op THAT'S NOT JAVASCRIPT!?
  8. 24.
  9. 25.
  10. 28.

    const {parse} = require('babylon'); const code = ` const year

    = (new Date()).getFullYear(); const msg = ':tada: Frontend Con ${year}! :tada:'; console.log(msg); `; const ast = parse(code); ./my-own-code-transform/index.js
  11. 30.

    “In computer science, an abstract syntax tree (AST), [...], is

    a tree representation of the abstract syntactic structure of source code written in a programming language.
  12. 31.
  13. 32.

    { "type": "Program", "start": 0, "end": 105, "loc": { "start":

    { "line": 1, "column": 0 }, "end": { "line": 3, "column": 17 } } }
  14. 33.

    { "type": "VariableDeclaration", "start": 0, "end": 40, "loc": { "start":

    { "line": 1, "column": 0 }, "end": { "line": 1, "column": 40 } }, "declarations": [...], "kind": "const" }
  15. 34.

    { "type": "ExpressionStatement", "start": 88, "end": 105, "loc": { "start":

    { "line": 3, "column": 0 }, "end": { "line": 3, "column": 17 } }, "expression": {...} }
  16. 40.

    ./example/input.js VariableDeclarator const msg = `:tada: Frontend Con ${year}! :tada:`;

    VariableDeclaration Identifier TemplateLiteral TemplateElement
  17. 41.

    ./example/input.js VariableDeclarator const msg = `:tada: Frontend Con ${year}! :tada:`;

    VariableDeclaration Identifier TemplateLiteral TemplateElement Identifier
  18. 42.

    ./example/input.js VariableDeclarator const msg = `:tada: Frontend Con ${year}! :tada:`;

    VariableDeclaration Identifier TemplateLiteral TemplateElement TemplateElement Identifier
  19. 49.

    const {parse} = require('babylon'); const code = `...`; const ast

    = parse(code); ./my-own-code-transform/index.js
  20. 50.

    const {parse} = require('babylon'); const {default: traverse} = require('@babel/traverse'); const

    code = `...`; const ast = parse(code); traverse(ast, { enter ({node}) { if (node.type === 'TemplateElement') { node.value.raw = node.value.raw.replace(/:tada:/g, '\u{1F389}'); } } }); ./my-own-code-transform/index.js
  21. 51.

    const {parse} = require('babylon'); const {default: traverse} = require('@babel/traverse'); const

    code = `...`; const ast = parse(code); traverse(ast, { enter ({node}) { if (node.type === 'TemplateElement') { node.value.raw = node.value.raw.replace(/:tada:/g, '\u{1F389}'); } } }); ./my-own-code-transform/index.js
  22. 53.

    const {parse} = require('babylon'); const {default: traverse} = require('@babel/traverse'); const

    code = `...`; const ast = parse(code); traverse(ast, { enter (path) { /* ... */ } }); ./my-own-code-transform/index.js
  23. 54.

    ./my-own-code-transform/index.js const {parse} = require('babylon'); const {default: traverse} = require('@babel/traverse');

    const {default: generate} = require('@babel/generator'); const code = `...`; const ast = parse(code); traverse(ast, { enter (path) { /* ... */ } }); console.log(generate(ast).code);
  24. 55.
  25. 57.

    ./lib/index.js export default function () { return { visitor: {

    TemplateElement({node}) { node.value.raw = node.value.raw.replace( /:tada:/g, '\u{1F389}' ); } } }; }
  26. 58.

    export default function () { return { visitor: { TemplateElement({node})

    { node.value.raw = node.value.raw.replace( /:tada:/g, '\u{1F389}' ); } } }; } ./lib/index.js
  27. 59.

    export default function () { return { visitor: { TemplateElement({node})

    { node.value.raw = node.value.raw.replace( /:tada:/g, '\u{1F389}' ); } } }; } ./lib/index.js
  28. 60.

    export default function () { return { visitor: { TemplateElement({node})

    { node.value.raw = node.value.raw.replace( /:tada:/g, '\u{1F389}' ); } } }; } ./lib/index.js
  29. 64.

    const App = () => { return <h1>:tada: Frontend Con

    2018! :tada:</h1>; }; babel-plugin-transform-react-jsx ReturnStatement
  30. 65.

    const App = () => { return <h1>:tada: Frontend Con

    2018! :tada:</h1>; }; babel-plugin-transform-react-jsx ReturnStatement JSXElement
  31. 66.

    const App = () => { return <h1>:tada: Frontend Con

    2018! :tada:</h1>; }; babel-plugin-transform-react-jsx ReturnStatement JSXElement JSXOpeningElement
  32. 67.

    const App = () => { return <h1>:tada: Frontend Con

    2018! :tada:</h1>; }; babel-plugin-transform-react-jsx ReturnStatement JSXElement JSXOpeningElement JSXText
  33. 68.

    const App = () => { return <h1>:tada: Frontend Con

    2018! :tada:</h1>; }; babel-plugin-transform-react-jsx ReturnStatement JSXElement JSXOpeningElement JSXText JSXClosingElement
  34. 69.

    const App = () => { return React.createElement( "h1", null,

    "! Frontend Con 2018! !" ); }; const App = () => { return <h1>:tada: Frontend Con 2018! :tada:</h1>; };
  35. 70.

    const App = () => { return <h1>:tada: Frontend Con

    2018! :tada:</h1>; }; const App = () => { return React.createElement( "h1", null, "! Frontend Con 2018! !" ); };
  36. 71.

    const App = () => { return <h1>:tada: Frontend Con

    2018! :tada:</h1>; }; const App = () => { return React.createElement( "h1", null, "! Frontend Con 2018! !" ); }; REACT?
  37. 72.

    /* @jsx heroify */ const App = () => {

    return <h1>:tada: Frontend Con 2018! :tada:</h1>; }; babel-plugin-transform-react-jsx
  38. 73.

    /* @jsx heroify */ const App = () => {

    return heroify( "h1", null, "! Frontend Con 2018! !" ); }; /* @jsx heroify */ const App = () => { return <h1>:tada: Frontend Con 2018! :tada:</h1>; };
  39. 74.

    /* @jsx h */ const App = () => {

    return <h1>:tada: Frontend Con 2018! :tada:</h1>; }; /* @jsx h */ const App = () => { return h( "h1", null, "! Frontend Con 2018! !" ); };
  40. 75.

    /* @jsx h */ import {h} from 'preact'; const App

    = () => { return <h1>:tada: Frontend Con 2018! :tada:</h1>; }; /* @jsx h */ import {h} from 'preact'; const App = () => { return h( "h1", null, "! Frontend Con 2018! !" ); };
  41. 76.

    /* @jsx h */ import {h} from 'preact'; const App

    = () => { return <h1>:tada: Frontend Con 2018! :tada:</h1>; }; /* @jsx h */ import {h} from 'preact'; const App = () => { return h( "h1", null, "! Frontend Con 2018! !" ); }; JSX is only one option
  42. 77.
  43. 80.

    <!DOCTYPE html> <html> <head> <title>You don't need JSX</title> <link rel="stylesheet"

    href="https: //contentful.github.io/ .../cf-extension.css"> <script src="https: //unpkg.com/contentful-ui-extensions-sdk@3"> </script> </head> <body> </body> </html>
  44. 81.

    <!DOCTYPE html> <html> <head> <title>You don't need JSX</title> <link rel="stylesheet"

    href="https: //contentful.github.io/ .../cf-extension.css"> <script src="https: //unpkg.com/contentful-ui-extensions-sdk@3"> </script> <script> /* Preact 8.2.9 */ !function () { "use strict"; function e() { } function ... </script> </head> <body> </body> </html>
  45. 82.

    <!DOCTYPE html> <html> <head> <title>You don't need JSX</title> <link rel="stylesheet"

    href="https: //contentful.github.io/ .../cf-extension.css"> <script src="https: //unpkg.com/contentful-ui-extensions-sdk@3"> </script> <script> /* Preact 8.2.9 */ !function () { "use strict"; function e() { } function ... </script> </head> <body> <script> const { h, render } = window.preact; </script> </body> </html>
  46. 83.

    <!DOCTYPE html> <html> <head> <title>You don't need JSX</title> <link rel="stylesheet"

    href="https: //contentful.github.io/ .../cf-extension.css"> <script src="https: //unpkg.com/contentful-ui-extensions-sdk@3"> </script> <script> /* Preact 8.2.9 */ !function () { "use strict"; function e() { } function ... </script> </head> <body> <script> const { h, render } = window.preact; render( h('h1', {class: 'headline-1'}, '! Frontend Con 2018! !'), document.body ); </script> </body> </html>
  47. 86.
  48. 89.

    const h = (nodeName, attributes, ...children) => { const el

    = document.createElement(nodeName); return el; }; ./our-hyperscript.js
  49. 90.

    const h = (nodeName, attributes, ...children) => { const el

    = document.createElement(nodeName); for (let key in attributes) { el.setAttribute(key, attributes[key]); } return el; }; ./our-hyperscript.js
  50. 95.

    const h = (nodeName, attributes, ...children) => { const el

    = document.createElement(nodeName); for (let key in attributes) { el.setAttribute(key, attributes[key]); } return el; }; ./our-hyperscript.js
  51. 96.

    const h = (nodeName, attributes, ...children) => { const el

    = document.createElement(nodeName); for (let key in attributes) { el.setAttribute(key, attributes[key]); } children.forEach(child => { if (typeof child === 'string') { el.appendChild(document.createTextNode(child)); } else { el.appendChild(child); } }); return el; }; ./our-hyperscript.js
  52. 97.

    import h from './our-hyperscript.js'; const App = () => {

    return h( "h1", null, "! Frontend Con 2018! !" ); }; console.log('App()', App()); ./app.js
  53. 98.

    import h from './our-hyperscript.js'; const App = () => {

    return h( "h1", null, "! Frontend Con 2018! !" ); }; console.log('App()', App()); ./app.js
  54. 99.

    const App = () => { return h( "h1", null,

    "! Frontend Con 2018! !" ); }; ./app.js
  55. 100.

    const App = (props) => { const {list} = props;

    return h('div', {class: 'app'}, h('h1', null, '! Frontend Con 2018! !'), h('ul', null, ...list.map(item => h('li', null, item)) ) ); }; ./app.js
  56. 101.

    ./app.js const App = (props) => { const {list} =

    props; return ( <div class="app">, <h1>! Frontend Con 2018! !</h1> <ul> {...list.map(item => <li>{item}</li>)} </ul> </div> ); };
  57. 102.

    const App = (props) => { const {list} = props;

    return h('div', {class: 'app'}, h('h1', null, '! Frontend Con 2018! !'), h('ul', null, ...list.map(item => h('li', null, item)) ); ); }; ./app.js
  58. 104.

    let currentApp; const App = (props) => {...}; const render

    = (state) => { const newApp = App(state); if (currentApp) { document.body.replaceChild(newApp, currentApp); } else { document.body.appendChild(newApp); } currentApp = newApp; }; ./app.js
  59. 106.

    let currentApp; const App = (props) => {...}; const render

    = (state) => {...}; const state = { list: [ '"', '#', '$', '%', '&', ''', '(', ')', '*', '+' ] }; ./app.js
  60. 107.

    let currentApp; const App = (props) => {...}; const render

    = (state) => {...}; const state = { list: [ '"', '#', '$', '%', '&', ''', '(', ')', '*', '+' ] }; render(state); ./app.js
  61. 108.
  62. 110.

    let currentApp; const App = (props) => {...}; const render

    = (state) => {...}; const state = {...}; render(state); ./app.js
  63. 111.

    let currentApp; const App = (props) => {...}; const render

    = (state) => {...}; const state = {...}; render(state); setInterval(_ => { state.list = [ ...state.list, getRandomItemFromArray(state.list) ]; render(state); }, 1000); ./app.js
  64. 112.
  65. 114.

    “We want to render components and have them updated only

    when data changes - that's where the power of vDOM diffing shines.
  66. 115.
  67. 118.

    Features Community Performance Which one should I use? DX Documentation

    Ecosystem Stability There are always trade-offs...
  68. 119.

    h()

  69. 120.

    const h = (nodeName, attributes, ...children) => { const el

    = document.createElement(nodeName); for (let key in attributes) { el.setAttribute(key, attributes[key]); } children.forEach(child => { if (typeof child === 'string') { el.appendChild(document.createTextNode(child)); } else { el.appendChild(child); } }) return el; } ./our-vdom.js
  70. 121.

    ./our-vdom.js const h = (nodeName, attributes, ...children) => { return

    {nodeName, attributes, children}; } h(...) → virtual DOM nodes
  71. 122.

    const App = (props) => { const {list} = props;

    return h('div', {class: 'app'}, h('h1', null, '! Frontend Con 2018! !'), h('ul', null, ...list.map(item => h('li', null, item)) ) ); }; ./app.js
  72. 123.

    const App = (props) => {...}; ./app.js { "nodeName": "div",

    "attributes": { "class": "app" }, "children": [ { "nodeName": "h1", "attributes": null, "children": ["! Frontend Con 2018! !"] }, { "nodeName": "ul", "attributes": null, "children": [ { "nodeName": "li", "attributes": null, "children": ["""] },
  73. 125.

    const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; } ./our-vdom.js
  74. 126.

    const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); } ./our-vdom.js
  75. 127.

    const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); el = document.createElement(nodeName); } ./our-vdom.js
  76. 128.

    const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); el = document.createElement(nodeName); for (let key in attributes) { el.setAttribute(key, attributes[key]); } } ./our-vdom.js
  77. 129.

    const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); el = document.createElement(nodeName); for (let key in attributes) { el.setAttribute(key, attributes[key]); } (children || []).forEach(c => el.appendChild(renderNode(c))); return el; } ./our-vdom.js
  78. 130.

    const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); el = document.createElement(nodeName); for (let key in attributes) { el.setAttribute(key, attributes[key]); } (children || []).forEach(c => el.appendChild(renderNode(c))); return el; } ./our-vdom.js
  79. 131.

    let currentApp; const App = (props) => {...}; const render

    = (state) => { const newApp = App(state); if (currentApp) { document.body.replaceChild(newApp, currentApp); } else { document.body.appendChild(newApp); } currentApp = newApp; }; ./app.js
  80. 132.

    let currentApp; const App = (props) => {...}; const render

    = (state) => { const newApp = renderNode(App(state)); if (currentApp) { document.body.replaceChild(newApp, currentApp); } else { document.body.appendChild(newApp); } currentApp = newApp; }; ./app.js
  81. 135.

    ./app.js const App = ({list}) => { return h('div', {class:

    'app'}, h('h1', null, '! Frontend Con 2018! !'), h('ul', null, ...list.map(item => h('li', null, item)) ); ); };
  82. 136.

    ./app.js class App { constructor () { this.state = {

    list: ['"', '#', '$', ...] }; } render (props, state) { const {list} = state; return h('div', {class: 'app'}, h('h1', null, '! Frontend Con 2018! !'), h('ul', null, ...list.map(item => h('li', null, item)) ) ); } }
  83. 137.

    ./app.js class App { constructor () { this.state = {

    list: ['"', '#', '$', ...] }; } render (props, {list}) { return h('div', {class: 'app'}, h('h1', null, '! Frontend Con 2018! !'), h('ul', null, ...list.map(item => h('li', null, item)) ) ); } }
  84. 138.

    const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); el = document.createElement(nodeName); for (let key in attributes) { el.setAttribute(key, attributes[key]); } (children || []).forEach(c => el.appendChild(renderNode(c))); return el; } ./our-vdom.js
  85. 139.

    const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); if (typeof nodeName === 'string') { el = document.createElement(nodeName); for (let key in attributes) { el.setAttribute(key, attributes[key]); } (children || []).forEach(c => el.appendChild(renderNode(c))); } return el; } ./our-vdom.js
  86. 140.

    const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); if (typeof nodeName === 'string') { // ... } return el; } ./our-vdom.js
  87. 141.

    const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); if (typeof nodeName === 'string') { // ... } else if (typeof nodeName === 'function') { // ... } return el; } ./our-vdom.js
  88. 142.

    const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); if (typeof nodeName === 'string') { // ... } else if (typeof nodeName === 'function') { // ... } return el; } ./our-vdom.js overreacted.io/how-does-react-tell-a-class-from-a-function/
  89. 143.

    const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); if (typeof nodeName === 'string') { // ... } else if (typeof nodeName === 'function') { // ... } return el; } ./our-vdom.js
  90. 144.

    const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); if (typeof nodeName === 'string') { // ... } else if (typeof nodeName === 'function') { const component = new nodeName(attributes); el = renderNode(component.render(component.props, component.state)); component.base = el; } return el; } ./our-vdom.js
  91. 145.

    const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); if (typeof nodeName === 'string') { // ... } else if (typeof nodeName === 'function') { const component = new nodeName(attributes); el = renderNode(component.render(component.props, component.state)); component.base = el; } return el; } ./our-vdom.js
  92. 146.

    const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); if (typeof nodeName === 'string') { // ... } else if (typeof nodeName === 'function') { const component = new nodeName(attributes); el = renderNode(component.render(component.props, component.state)); component.base = el; } return el; } ./our-vdom.js A COMPONENT HOLDS A DOM REFERENCE
  93. 147.
  94. 148.

    ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; } render (props, {list}) { return h('div', {class: 'app'}, h('h1', null, '! Frontend Con 2018! !'), h('ul', null, ...list.map(item => h('li', null, item)) ) ); } }
  95. 149.

    ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; } render (props, {list}) {...} }
  96. 150.

    ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; this.timer = setInterval(_ => { this.setState({ list: [...this.state.list, getRandomItemFromArray(state.list)] }); }, 1000); } render (props, {list}) {...} }
  97. 151.

    ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; this.timer = setInterval(_ => { this.setState({ list: [...this.state.list, getRandomItemFromArray(state.list)] }); }, 1000); } setState(state) { this.state = Object.assign(this.state, state); renderComponent(this); } render (props, {list}) {...} }
  98. 152.

    ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; this.timer = setInterval(_ => { this.setState({ list: [...this.state.list, getRandomItemFromArray(state.list)] }); }, 1000); } setState(state) { this.state = Object.assign(this.state, state); renderComponent(this); } render (props, {list}) {...} }
  99. 156.

    const renderComponent = (component) => { const oldBase = component.base;

    component.base = renderNode( component.render(component.props, component.state) ); }; ./our-vdom.js
  100. 157.

    const renderComponent = (component) => { const oldBase = component.base;

    component.base = renderNode( component.render(component.props, component.state) ); oldBase.parentNode.replaceChild(component.base, oldBase); }; ./our-vdom.js
  101. 158.

    const renderComponent = (component) => { const oldBase = component.base;

    component.base = renderNode( component.render(component.props, component.state) ); oldBase.parentNode.replaceChild(component.base, oldBase); } COMPONENTS 
 RENDER 
 AUTONOMOUSLY ./our-vdom.js
  102. 161.
  103. 163.
  104. 164.

    const renderComponent = (component) => { const oldBase = component.base;

    component.base = renderNode( component.render(component.props, component.state) ); oldBase.parentNode.replaceChild(component.base, oldBase); }; ./our-vdom.js
  105. 165.

    const renderComponent = (component) => { const oldBase = component.base;

    component.base = renderNode( component.render(component.props, component.state) ); oldBase.parentNode.replaceChild(component.base, oldBase); }; DOM ./our-vdom.js
  106. 166.

    const renderComponent = (component) => { const oldBase = component.base;

    component.base = renderNode( component.render(component.props, component.state) ); oldBase.parentNode.replaceChild(component.base, oldBase); }; virtual DOM ./our-vdom.js
  107. 167.

    { nodeName: 'div', attributes: { key: 'value' }, children: [

    'text node' ] } Element( nodeName: 'div', attributes: { getItem('key'), setItem('key', 'value') }, childNodes: [ TextNode('text node') ] ) DOM virtual DOM ./our-vdom.js
  108. 168.

    const renderComponent = (component) => { const oldBase = component.base;

    component.base = renderNode( component.render(component.props, component.state) ); oldBase.parentNode.replaceChild(component.base, oldBase); }; ./our-vdom.js
  109. 170.

    const renderComponent = (component) => { const rendered = component.render(

    component.props, component.state ); }; ./our-vdom.js
  110. 171.

    const renderComponent = (component) => { const rendered = component.render(

    component.props, component.state ); diff(component.base, rendered); }; ./our-vdom.js
  111. 172.

    const renderComponent = (component) => { const rendered = component.render(

    component.props, component.state ); component.base = diff(component.base, rendered); }; ./our-vdom.js
  112. 174.

    const diff = (dom, vnode) => { const newDom =

    renderNode(vnode); dom.parentNode.replaceChild(newDom, dom); return newDom; }; ./our-vdom.js
  113. 175.

    const diff = (dom, vnode) => { const hasNewKids =

    dom.childNodes.length !== vnode.children.length; if (hasNewKids) { } else { const newDom = renderNode(vnode); dom.parentNode.replaceChild(newDom, dom); return newDom; } }; ./our-vdom.js
  114. 176.

    const diff = (dom, vnode) => { const hasNewKids =

    dom.childNodes.length !== vnode.children.length; if (hasNewKids) { dom.appendChild( renderNode(vnode.children[vnode.children.length - 1]) ); return dom; } else { const newDom = renderNode(vnode); dom.parentNode.replaceChild(newDom, dom); return newDom; } }; ./our-vdom.js
  115. 177.
  116. 179.

    DOM { nodeName: 'div', attributes: { key: 'value' }, children:

    [ 'text node' ] } Element( nodeName: 'div', attributes: { getItem('key'), setItem('key', 'value') }, childNodes: [ TextNode('text node') ] ) virtual DOM
  117. 180.

    DOM { nodeName: 'div', attributes: { key: 'value' }, children:

    [ 'text node' ] } Element( nodeName: 'div', attributes: { getItem('key'), setItem('key', 'value') }, childNodes: [ TextNode('text node') ] ) virtual DOM
  118. 181.

    DOM { nodeName: 'div', attributes: { key: 'value' }, children:

    [ 'text node' ] } Element( nodeName: 'div', attributes: { getItem('key'), setItem('key', 'value') }, childNodes: [ TextNode('text node') ] ) virtual DOM
  119. 182.

    DOM { nodeName: 'div', attributes: { key: 'value' }, children:

    [ 'text node' ] } Element( nodeName: 'div', attributes: { getItem('key'), setItem('key', 'value') }, childNodes: [ TextNode('text node') ] ) virtual DOM
  120. 183.

    ./app.js const diff = (dom, vnode) => { const hasNewKids

    = dom.childNodes.length !== vnode.children.length; if (hasNewKids) { dom.appendChild( renderNode(vnode.children[vnode.children.length - 1]) ); return dom; } else { const newDom = renderNode(vnode); dom.parentNode.replaceChild(newDom, dom); return newDom; } };
  121. 186.
  122. 187.

    ./app.js const diff = (dom, vnode) => { // compare

    type // compare attributes // compare children };
  123. 188.

    ./app.js const diff = (dom, vnode) => { // compare

    type // compare attributes // compare children // optimise as much as you can! };
  124. 191.

    ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; this.timer = setInterval(_ => { this.setState({ list: [...this.state.list, getRandomItemFromArray(state.list)] }); }, 1000); } setState(state) { this.state = Object.assign(this.state, state); renderComponent(this); } render (props, {list}) {...} }
  125. 192.

    ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; this.timer = setInterval(_ => { this.setState({ list: [...this.state.list, getRandomItemFromArray(state.list)] }); }, 1000); } setState(state) { this.prevState = Object.assign({}, this.state); this.state = Object.assign(this.state, state); renderComponent(this); } render (props, {list}) {...} }
  126. 193.

    const renderComponent = (component) => { const rendered = component.render(

    component.props, component.state ); component.base = diff(component.base, rendered); }; ./our-vdom.js
  127. 194.

    const renderComponent = (component) => { const {state, props} =

    component; component.state = component.prevState; component.props = component.prevProps; const rendered = component.render( component.props, component.state ); component.base = diff(component.base, rendered); }; ./our-vdom.js
  128. 195.

    const renderComponent = (component) => { const {state, props} =

    component; component.state = component.prevState; component.props = component.prevProps; const rendered = component.render( component.props, component.state ); component.base = diff(component.base, rendered); }; ./our-vdom.js
  129. 196.

    const renderComponent = (component) => { const {state, props} =

    component; component.state = component.prevState; component.props = component.prevProps; const rendered = component.render( component.props, component.state ); component.base = diff(component.base, rendered); }; ./our-vdom.js
  130. 197.

    const renderComponent = (component) => { const {state, props} =

    component; component.state = component.prevState; component.props = component.prevProps; const rendered = component.render( component.props, component.state ); component.base = diff(component.base, rendered); }; ./our-vdom.js
  131. 198.

    const renderComponent = (component) => { const {state, props} =

    component; component.state = component.prevState; component.props = component.prevProps; if (component.shouldComponentUpdate(props, state)) { component.props = props; component.state = state; const rendered = component.render( component.props, component.state ); component.base = diff(component.base, rendered); } }; ./our-vdom.js
  132. 199.

    ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; this.timer = setInterval(_ => { this.setState({ list: [...this.state.list, getRandomItemFromArray(state.list)] }); }, 1000); } setState(state) { this.prevState = Object.assign({}, state); this.state = Object.assign(this.state, state); renderComponent(this); } render (props, {list}) {...} }
  133. 200.

    ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; this.timer = setInterval(_ => { this.setState({ list: [...this.state.list, getRandomItemFromArray(state.list)] }); }, 1000); } setState(state) {...} render (props, {list}) {...} }
  134. 201.

    ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; this.timer = setInterval(_ => { this.setState({ list: [...this.state.list, getRandomItemFromArray(state.list)] }); }, 1000); } shouldComponentUpdate(nextProps, nextState) { return this.state.list.length !== nextState.list.length; } setState(state) {...} render (props, {list}) {...} }
  135. 203.

    parcelRequire=function(e,r,n){var t="function"==typeof parcelRequire&&parcelRequire,i="function"==typeof require&&require;function u(n,o){if(!r[n]){if(!e[n]) {var f="function"==typeof parcelRequire&&parcelRequire;if(!o&&f)return f(n,!0);if(t)return t(n,!0);if(i&&"string"==typeof

    n)return i(n);var c=new Error("Cannot find module '"+n+"'");throw c.code="MODULE_NOT_FOUND",c}a.resolve=function(r){return e[n][1][r]||r};var l=r[n]=new u.Module(n);e[n] [0].call(l.exports,a,l,l.exports)}return r[n].exports;function a(e){return u(a.resolve(e))}}u.isParcelRequire=!0,u.Module=function(e) {this.id=e,this.bundle=u,this.exports={}},u.modules=e,u.cache=r,u.parent=t;for(var o=0;o<n.length;o++)u(n[o]);return u}({4:[function(require,module,exports) { var e=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=! 0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();function t(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}function n(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function r(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:! 1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function o(e,t){if(!(e instanceof t))throw new T y p e E r r o r ( " C a n n o t c a l l a c l a s s a s a f u n c t i o n " ) } v a r i = f u n c t i o n ( e , t ) { f o r ( v a r n = a r g u m e n t s . l e n g t h , r = A r r a y ( n > 2 ? n - 2 : 0 ) , o = 2 ; o < n ; o + +)r[o-2]=arguments[o];return{nodeName:e,attributes:t,children:r}},a=function(e){var t=e.base,n=e.render(e.props,e.state),r=u(t,n);e.base=r},u=function e(t,n,r) {if(t){if("string"==typeof n)return t.nodeValue=n,t;if("function"==typeof n.nodeName){var o=new n.nodeName(n.attributes);return e(t,o.render(o.props,o.state)),t}t.childNodes.length!==n.children.length&&t.appendChild(f(n.children[n.children.length-1]));for(var i=0;i<n.children.length;i+ +)e(t.childNodes[i],n.children[i]);return t}var a=f(n);return r.appendChild(a),a},s=function(){function t(e){o(this,t),this.props=e,this.state={}}return e(t, [{key:"setState",value:function(e){this.state=Object.assign(this.state,e),a(this)}}]),t}(),l=function(a){function u(e){o(this,u);var r=n(this,(u.__proto__|| Object.getPrototypeOf(u)).call(this,e));return r.state.list=["" ","# ","$ ","% ","& ","' ","( ",") ","* ","+ "],r.timer=setInterval(function() {r.setState({list:[].concat(t(r.state.list),[r.state.list[Math.floor(Math.random()*r.state.list.length)]])})},1e3),r}return r(u,s),e(u, [{key:"render",value:function(e,t){var n=t.list;return i("div",{class:"app"},i("h1",null,"! JSHeroes 2018! ! "),i(c,{list:n}))}}]),u}(),c=function(a){function u(){return o(this,u),n(this,(u.__proto__||Object.getPrototypeOf(u)).apply(this,arguments))}return r(u,s),e(u,[{key:"render",value:function(e,n){return i . a p p l y ( v o i d 0 , [ " u l " , n u l l ] . c o n c a t ( t ( e . l i s t . m a p ( f u n c t i o n ( e ) { r e t u r n i ( " l i " , n u l l , e ) } ) ) ) ) } } ] ) , u } ( ) , f = f u n c t i o n e ( t ) { v a r n = v o i d 0 , r = t . n o d e N a m e , o = t . a t t r i b u t e s , i = t . c h i l d r e n ; i f ( t . s p l i t ) r e t u r n d o c u m e n t . c r e a t e T e x t N o d e ( t ) ; i f ( " s t r i n g " = = t y p e o f r ) f o r ( k e y i n n = d o c u m e n t . c r e a t e E l e m e n t ( r ) , o ) " o " = = k e y [ 0 ] & & " n " = = k e y [ 1 ] ? n . a d d E v e n t L i s t e n e r ( k e y . t o L o w e r C a s e ( ) . s u b s t r i n g ( 2 ) , o [ k e y ] ) : " c l a s s " = = = k e y ? n.className=o[key]:n.setAttribute(key,o[key]);else if("function"==typeof r){var a=new r(o);n=e(a.render(a.props,a.state)),a.base=n}return(i|| []).forEach(function(t){return n.appendChild(e(t))}),n},p=function(e,t){u(void 0,e,t)};p(i(l),document.body); },{}]},{},[4]) app.min.js vDOM example on Codepen
  136. 205.
  137. 207.

    export default class App extends Component { ... render(props, {

    highlighted }) { return ( <button type="button" highlighted={highlighted} onclick={this.toggleHighlight}> { highlighted ? 'Please stop!': 'Highlight me!' } </button> ); } } codesandbox.io/s/rwjol2n3op
  138. 208.

    codesandbox.io/s/rwjol2n3op export default class App extends Component { ... render(props,

    { highlighted }) { return React.createElement( 'button', { type: 'button', highlighted: highlighted, onclick: this.toggleHighlight }, highlighted ? 'Please stop!' : 'Highlight me!' ); } }
  139. 209.

    codesandbox.io/s/rwjol2n3op export default class App extends Component { ... render(props,

    { highlighted }) { return React.createElement( 'button', { type: 'button', highlighted: highlighted, onclick: this.toggleHighlight }, highlighted ? 'Please stop!' : 'Highlight me!' ); } } It's "just" function calls
  140. 213.

    ./app.js const diff = (dom, vnode) => { // compare

    type // compare attributes // compare children }
  141. 218.

    import { html, render } from 'lit-html'; const data =

    { list: ['"', ..., '+'] }; const App = ({list}) => { return html` <div class="app"> <h1>! Frontend Con 2018! !</h1> <ul> ${ list.map(item => html`<li>${ item }</li>`) } </ul> </div> ` };
  142. 219.

    import { html, render } from 'lit-html'; const data =

    { list: ['"', ..., '+'] }; const App = ({list}) => { return html` <div class="app"> <h1>! Frontend Con 2018! !</h1> <ul> ${ list.map(item => html`<li>${ item }</li>`) } </ul> </div> ` }; render(App(data), document.body);
  143. 226.

    html`<li>${ '"' } ${ Math.random() }</li>` function html(chunks, ...interpolations) {

    // diff only things that can change } [['<li>', ' ', '</li>' ], '"', 0.62...]
  144. 227.

    html`<li>${ '"' } ${ Math.random() }</li>` function html(chunks, ...interpolations) {

    // diff only things that can change } [['<li>', ' ', '</li>' ], '"', 0.62...]
  145. 228.

    html`<li>${ '"' } ${ Math.random() }</li>` function html(chunks, ...interpolations) {

    // diff only things that can change } [['<li>', ' ', '</li>' ], '"', 0.62...]