Skip to content

Instantly share code, notes, and snippets.

@jimfranke
Forked from developit/little-vdom.js
Last active March 3, 2020 13:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jimfranke/7a313d6016164165f454d5e66cea78f0 to your computer and use it in GitHub Desktop.
Save jimfranke/7a313d6016164165f454d5e66cea78f0 to your computer and use it in GitHub Desktop.
const h = (type, props, ...children) => ({
type, props, children
});
const render = (newVNode, node, oldVNode, insertIdx) => {
oldVNode = oldVNode || {};
let propValue;
// arrays
return [newVNode].flat(Infinity).map(newVNode => (
// text nodes
typeof newVNode === 'string' && (newVNode = h(null, newVNode)),
// components
typeof newVNode.type === 'function'
? (
newVNode.i = render(
newVNode.type(
{ children: newVNode.children, ...newVNode.props },
newVNode.state = oldVNode.state || {},
nextState => render(
(newVNode.state = { ...newVNode.state, ...nextState }) && newVNode,
node,
newVNode
)
),
oldVNode.i || node,
oldVNode.i || {}
),
newVNode
)
: (
// create nodes
newVNode.node = oldVNode.node || (newVNode.type != null
? document.createElement(newVNode.type)
: document.createTextNode(newVNode.props)
),
// diff props
newVNode.props != oldVNode.props && (newVNode.type != null
? Object.keys(newVNode.props || {}).map(prop =>
(propValue = newVNode.props[prop]) != (oldVNode.props?.[prop]) && (
prop in newVNode.node
? newVNode.node[prop] = propValue
: newVNode.node.setAttribute(prop, propValue)
)
)
: newVNode.node.data = newVNode.props
),
// insert at position
oldVNode.node && insertIdx == null || node.insertBefore(
newVNode.node,
node.childNodes[insertIdx + 1]
),
// diff children
newVNode.o = newVNode.children.concat.apply([], newVNode.children)
.map((newVChild, i) => render(
newVChild = newVChild.children
? newVChild
: h(null, newVChild),
newVNode.node,
oldVNode.o?.find((oldVChild, j) =>
oldVChild?.type == newVChild.type
&& oldVChild.key == newVChild.key
&& (j == i
&& (i = null),
oldVNode.o[j] = 0,
oldVChild
)
) || {},
i
)
),
// remove nodes
oldVNode.o?.map(oldVNode =>
oldVNode && oldVNode.node.remove()
),
{ ...oldVNode, ...newVNode }
)
)
)[0];
};
const Fragment = ({ children }) => children;
const Wrap = (props, state, setState) => {
const { children } = props;
const {
time = new Date(),
kids = []
} = state;
return (
<div>
<time>{time?.toLocaleTimeString()}</time>
<button onclick={() => setState({ time: new Date() })}>
Update
</button>
<button
onclick={() => setState({
kids: kids.concat(
<div key={kids.length}>
hello {kids.length + 1}
</div>
)})}
>
Add
</button>
<button
onclick={() => {
if (kids.length < 2) {
return;
}
let newChildren = [...kids];
let secondLast = newChildren[newChildren.length - 2];
newChildren[kids.length - 2] = newChildren[1];
newChildren[1] = secondLast;
setState({
kids: newChildren
});
}}
>
Swap
</button>
<div class="kids">
{kids}
</div>
<hr />
{children}
</div>
);
};
const CountButton = (props, state, setState) => {
const { count = 0 } = state;
return (
<div>
<button
count={count}
onclick={() => setState({ count: count + 1 })}
>
{count}
</button>
</div>
);
};
const Since = (props, state, setState) => {
const { time } = props;
setTimeout(setState, 1000);
const ago = (Date.now() - time) / 1000 | 0;
return (
<time>{ago}s ago</time>
);
};
/** @jsx h */
console.clear();
render(
<Fragment>
<h1>Virtual DOM</h1>
<Wrap>
<CountButton />
<CountButton />
<CountButton />
</Wrap>
Started:
<div>
<Since time={Date.now()} />
</div>
</Fragment>,
document.body
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment