Jason Miller

@developit  on Github

@_developit  on Twitter

serial library author

Constraints

are challenges

ADHD

into the void 0
;

JSX

XML-like expression

compiled to a function call


 <div id="one">
   Hello
 </div>

 h('div', { id:'one' },
   'Hello'
 );

JSX Explained

let world = 'World!';

<div one={ 1 }>
  Hello, { world }
</div>

let Link = 'a';
<Link>Test</Link>
var world = 'World!';

h('div', { one: 1 },
  'Hello, ', world
);

var Link = 'a';
h(Link, 'Test');

JSX Explained

JS expression

reference

  • AKA createElement()
  • Calls inserted in place of every <tag>

  h(
​    nodeName,    // String / Function
    attributes,  // Object / null
    ...children  // any remaining args
  )

Factory Function

So... hyperscript?


  h('div', { id:'one' },
    'Hello'
  )

  h('div', { id:'one' },
    'Hello'
  )

 h(Foo, { hello: true })

  h('div#one', 'Hello')

Hyperscript

Transpiled JSX

Virtual DOM

Virtual DOM

Objects representing

a DOM tree structure

Virtual DOM

JSX

hyperscript

Virtual DOM

syntax

function calls

objects

transform

invoke

We write this:

 <div id="foo">
   Hello!
   <br />
 </div>

... which transpiles to this:

 h('div', { id:'foo' },
   'Hello!',
   h('br')
 )

... which returns this:

 {
   nodeName:'div',
   attributes: { id:'foo' },
   children: [
     'Hello!',
     { nodeName:'br' }
   ]
 }

  function h(nodeName, attributes, ...children) {

    return { nodeName, attributes, children }

  }

Virtual DOM Factory

hyperscript

virtual DOM

 function render(vnode) {  
   if (typeof vnode==='string')
     return document.createTextNode(vnode)

   let node = document.createElement(vnode.nodeName)

   for (let name in Object(vnode.attributes))
     node.setAttribute(name, vnode.attributes[name])

   for (let i=0; i<vnode.children.length; i++)
     node.appendChild(render(vnode.children[i]))

   return node
 }

Simple Renderer

render( {
  nodeName:'div',
  attributes: { id:'foo' },
  children: [
    'Hello!',
    { nodeName:'br' }
  ]
} )
 function diffAttributes(node, vnode) {
   for (let name in Object(vnode.attributes))
     node.setAttribute(name, vnode.attributes[name])
 }

Attributes or Properties?

 function diffProperties(node, vnode) {
   Object.assign(node, vnode.attributes)
 }
 function diffProps(node, vnode) {
   for (let name in Object(vnode.attributes)) {
     let value = vnode.attributes[name]
     if (name in node) {
       node[name] = value
     }
     else {
       node.setAttribute(name, value)
     }
   }
 }

?

?

Does it work?


 let vdom = (
   <div id="foo">
     <h1>Hello</h1>
     <ol>
       <li>One</li>
       <li>Two</li>
     </ol>
   </div>
 )

 let dom = render(vdom)

 document.body.appendChild(dom)

YUP

Deploy It

Diffing

without magic

WHAT IF...

Instead of rendering,

we diffed the VDOM objects

against the current DOM

and applied any differences?

Virtual

Actual

 {
   nodeName: 'div',
   attributes: {
     key: 'value'
   },
   children: [
     'text node'
   ]
 }
 Element(
   nodeName: 'div',
   attributes: {
     getItem('key'),
     setItem('key', 'value')
   },
   childNodes: [
     TextNode('text node')
   ]
 )

DOM

DOM

Comparable

Diff Process

<type>
children
attributes / props

new / updated?

1

2

3

Diff: type

is node component?

diff()

create/update component

render component

same type?

no

yes

no

yes

create new & replace

update in-place

Diff: children

has key?

for each child node

no

yes

append to unkeyed

add to keyed map

find match in

keyed / unkeyed

for each virtual child

diff against virtual child

insert at current index

remove unused children

1

2

3

What are keys for?

keys attribute meaningful order to a set of uniform elements.

 <li>one</li>
 <li>two</li>
 <li>three</li>
 <li>one</li>
 <li>three</li>
 <li>three</li>
 <li>one</li>
 <li>three</li>
<li key="1">one
<li key="2">two
<li key="3">three
 <li>one</li>
 <li>two</li>
 <li>three</li>
<li key="1">one
<li key="3">three

Diff: attributes/props

for each
old attribute

not in new attributes?

set to undefined

for each
new attribute

differs from old value?

set to
new value

1

2

Performance

Benchmark everying

"The DOM is slow"

YOUR DOM is slow

"The DOM is slow"

DOMination

use Text nodes

for text


  // Create it
  text = document.createTextNode('some text')

  // Insert it
  parent.insertBefore(text, <before | null>)

  // Flip it & reverse it
  text.nodeValue = 'ʇxǝʇ ǝɯos'

Text.nodeValue

almost 50% faster

DOM wanna miss a thing

avoid (DOM) getters


  // slow
  if (node.nodeType===3) { }

  // fast
  if (node.splitText) { }

  // ludicrous speed
  if (node.splitText!==undefined) { }

breaks when cross-document

getters

simple prototype property

DOM & dommer

avoid Live NodeLists


 // slow
 for (let i=parent.childNodes.length; i--; ) {
   parent.removeChild(parent.childNodes[i])
 }

 // fast
 while ( child = parent.lastChild ) {
   parent.removeChild(child)
 }

(bench setup overhead)

childNodes

lastChild

Measure

Always be profiling

DevTools Timeline

Something to obsess over.

IRHydra

  • @mraleph
  • see how/why
  • shows deopts

git.io/hydraosx

ESBench.com

 = Babel + Benchmark.js

visualize

Lessons

Be Explicit

Don't accidentally ToBoolean


 // don't write this:
 if (obj.props)

 // if you mean this:
 if (obj.props!==undefined)

could be 0, '', null...

Inline Helpers

Functions can be too generic.


 function hook(obj, name, a, b) {
   return obj[name] && obj[name](a, b);
 }

 hook(obj, 'foo', 1, 2);
 hook(obj, 'foo', 'a', {b:'c'});


 obj.foo(1, 2);

deopts after a few different values

Short-Circuitting

The cheapest function call
is the one you never make

- unknown

Make decisions based on data

Fin

Preact: Into the void 0

By developit

Preact: Into the void 0

  • 8,897