into the void 0
;
@_developit on Twitter
@developit on GitHub
serial library author
(artistic liberty)
Look familiar?
This code was
written in 2008.
XML-like expression
compiled to a function call
<div id="one">
Hello
</div>
<Foo hello />
h('div', { id:'one' },
'Hello'
);
h(Foo, { hello: true })
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');
h(
nodeName, // String / Function
attributes, // Object / null
...children // any remaining args
)
h('div', { id:'one' },
'Hello'
)
h('div', { id:'one' },
'Hello'
)
h(Foo, { hello: true })
h('div#one', 'Hello')
Hyperscript
Transpiled JSX
Objects representing
a DOM tree structure
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 };
}
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
}
function diffAttributes(node, vnode) {
for (let name in Object(vnode.attributes))
node.setAttribute(name, vnode.attributes[name])
}
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)
}
}
}
?
?
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)
{
nodeName: 'div',
attributes: {
key: 'value'
},
children: [
'text node'
]
}
Element(
nodeName: 'div',
attributes: {
getItem('key'),
setItem('key', 'value')
},
childNodes: [
TextNode('text node')
]
)
DOM
DOM
Virtual DOM | DOM Declarative | DOM Imperative |
---|---|---|
nodeName | nodeName | createElement(nodeName) |
attributes[] | attributes{} | setAttribute(key, value)
getAttribute(key): value |
children[] | childNodes[] | appendChild(child)
removeChild(child) insertBefore(child, next) replaceChild(old, child) |
<type>
children
attributes / props
new / updated?
1
2
3
is node component?
diff()
create/update component
render component
same type?
no
yes
no
yes
create new & replace
update in-place
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
find child Element with same key
find next unused child of same type
no match? create a new element.
Component / Text / nodeName
<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
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
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'
almost 50% faster
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
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
Something to obsess over.
Don't accidentally ToBoolean
// don't write this:
if (obj.props)
// if you mean this:
if (obj.props!==undefined)
could be 0, '', null...
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
has its own inline cache
could be anything
The cheapest function call
is the one you never make
- unknown
monomorphic
fn({ a: 1 })
fn({ a: 2 })
megamorphic
fn({ a: 1, b: 2 })
fn({ b: 2, a: 1 })
fn('a', 1)
fn(div)
fn(span)
polymorphic
fn({ a: 1 })
fn({ b: 2 })
fn({ a: 1 })
fn({ a: 1 }, 1)
best
worst
better
fn = o => o.a
Monomorphic
Guarded property access
requestAnimationFrame
setTimeout
MutationObserver
Promise.then()
SCRAP
// like 0ms
let child = parent.lastChild
while (child) {
let prev = child.previousSibling
child.remove()
child = prev
}
has key?
for each child node
no
yes
append to unkeyed
add to keyed map
has key?
for each virtual child
no
yes
find match in unkeyed
find in keyed map
diff against virtual child
Accidentally wrote another template engine
Sad me
<div id="one">
Hello
</div>
<Foo hello />
h('div', { id:'one' },
'Hello'
);
h(Foo, { hello: true })
Remember hyperscript?
It's behind everything now
From blog post "WTF is JSX"
// a "partial" that does a filtered loop
function foo(items) {
return items.map( p => <li>{p}</li> );
}
let vdom = (
<div id="foo">
<p>Look, a simple JSX DOM renderer!</p>
<ul>{ foo(ITEMS) }</ul>
</div>
)
document.body.appendChild( render(vdom) );
Paul Lewis (@aerotwist)
Pot Stirrer
Decent
Surprisingly Decent
Debounced rendering
this was super
annoying to do
Paul Lewis
...again!
It seems to me that developer ergonomics should be less important than our users’ needs.
Idea
is vnode same type
diff
Component?
(function)
create/update component
render
same type
as DOM node?
same type
as DOM node?
no
yes
find match in
keyed / unkeyed
for each virtual child
diff against virtual child
insert at current index
function match(node, vnode) {
if (vnode.key!=null) {
return node.props.key === vnode.key
}
if (typeof vnode.nodeName==='function') {
return node._constructor === vnode.nodeName
}
return node.nodeName === vnode.nodeName
}