LBAVDL 07: Reconciliation, or patching the DOM

by wildlyinaccurate

HTML

<div id="app"></div>
<script>
window.addEventListener("load", () => {
    class Counter extends Component {
        state = { count: 0 };

        constructor(props) {
            super(props);
        }

        increment() {
            this.setState({ count: this.state.count + 1 });
        }

        render() {
            const buttonText = this.props.buttonText || "+1";

            return createVNode("div", { style: "padding: 0.25em 0;" }, [
                createVNode(
                    "input",
                    { placeholder: "Try typing here, then click the button..." },
                    []
                ),
                ` Count: ${this.state.count} `,
                createVNode("button", { onclick: () => this.increment() }, [
                    buttonText,
                ]),
            ]);
        }
    }

    function app() {
        return createVNode("div", { className: "container" }, [
            createVNode(Counter),
            createVNode(Counter, { buttonText: "Add One" }),
        ]);
    }

    mount(app(), document.getElementById("app"));
});
</script>

JavaScript

function createVNode(type, props, children) {
    return {
        type,
        props,
        children,
        _dom: null,
    };
}

// The mount function takes a vnode and mounts it to an existing "parent" DOM node
function mount(vnode, parentDom) {
    let domNode;

    if (typeof vnode === "string" || typeof vnode === "number") {
        // When the node is a string or number, we can insert a plain text node.
        domNode = document.createTextNode(vnode);
    } else {
        if (typeof vnode.type === "function") {
            // If the vnode type is a function, we can assume it is a component.
            domNode = createComponentNode(vnode, parentDom);
        } else {
            // For "regular" vnodes, we create a HTMLElement of the node's type.
            domNode = document.createElement(vnode.type);
        }

        // Store the DOM node on the VDOM node for future updates.
        vnode._dom = domNode;

        if (vnode.props) {
            // All of the props are set as attributes on the HTMLElement.
            for (const prop in vnode.props) {
                setProperty(domNode, prop, vnode.props[prop]);
            }
        }

        if (vnode.children) {
            // Any children go through the same process recursively until we have
            // mounted the whole tree.
            vnode.children.forEach((child) => mount(child, domNode));
        }
    }

    // When we're finished, append the new DOM node to the parent.
    parentDom.appendChild(domNode);

    return domNode;
}

function setProperty(domNode, prop, value) {
    if (prop === "style") {
        if (typeof value === "string") {
            // Set string styles on the cssText property
            domNode.style.cssText = value;
        } else {
            // Set object styles on the style object
            for (const styleProp in value) {
                domNode.style[styleProp] = value[styleProp];
           ...