52
loading...
This website collects cookies to deliver better user experience
Plug: I help develop million
: <1kb virtual DOM - it's fast!
m
, createElement
, patch
. To completely understand how virtual DOM works, let's try and create our own rudimentary virtual DOM based off of these functions (~7 minutes read time).m
function is a helper function that creates virtual elements. A virtual element contains three properties:tag
: which stores the tag name of the element as a string.props
: which stores the properties/attributes of the element as an object.children
: which stores virtual node children of the element as an array.m
helper function is below:const m = (tag, props, children) => ({
tag,
props,
children,
});
m('div', { id: 'app' }, ['Hello World']);
// Is the same as:
{
tag: 'div',
props: { id: 'app' },
children: ['Hello World']
}
createElement
function turns a virtual node into a real DOM element. This is important because we'll be using this in our patch
function and the user may also use it to initialize their application.createElement
helper function is below:const createElement = vnode => {
if (typeof vnode === 'string') {
return document.createTextNode(vnode); // Catch if vnode is just text
}
const el = document.createElement(vnode.tag);
if (vnode.props) {
Object.entries(vnode.props).forEach(([name, value]) => {
el[name] = value;
});
}
if (vnode.children) {
vnode.children.forEach(child => {
el.appendChild(createElement(child));
});
}
return el;
};
createElement(m('div', { id: 'app' }, ['Hello World']));
// Is the same as: <div id="app">Hello World</div>
patch
function takes an existing DOM element, old virtual node, and new virtual node. This won't necessarily be the most performant implementation, but this is just for demonstration purposes.patch
helper function is below:const patch = (el, oldVNode, newVNode) => {
const replace = () => el.replaceWith(createElement(newVNode));
if (!newVNode) return el.remove();
if (!oldVNode) return el.appendChild(createElement(newVNode));
// Handle text case
if (typeof oldVNode === 'string' || typeof newVNode === 'string') {
if (oldVNode !== newVNode) return replace();
} else {
// Diff tag
if (oldVNode.tag !== newVNode.tag) return replace();
// Diff props
if (!oldVNode.props?.some((prop) => oldVNode.props?[prop] === newVNode.props?[prop])) return replace();
// Diff children
[...el.childNodes].forEach((child, i) => {
patch(child, oldVNode.children?[i], newVNode.children?[i]);
});
}
}
const oldVNode = m('div', { id: 'app' }, ['Hello World']);
const newVNode = m('div', { id: 'app' }, ['Goodbye World']);
const el = createElement(oldVNode);
patch(el, oldVNode, newVNode);
// el will become: <div id="app">Goodbye World</div>
textContent
of elements to boost performance.