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
- 9,282