<div id="app"></div>
function createVNode(tag, props = {}, children = []) {
return { tag, props, children }
}
function mount(vnode, container) {
const element = (vnode.element = document.createElement(vnode.tag))
Object.entries(vnode.props || {}).forEach(([key, value]) => {
element.setAttribute(key, value)
})
if (typeof vnode.children === 'string') {
element.textContent = vnode.children
} else {
vnode.children.forEach(child => {
mount(child, element) // Recursively mount the children
})
}
container.appendChild(element)
}
function unmount(vnode) {
vnode.element.parentNode.removeChild(vnode.element)
}
function patch(VNode1, VNode2) {
// Assign the parent DOM element
const element = (VNode2.element = VNode1.element);
// Now we have to check the difference between the two vnodes
// If the nodes are of different tags, assume that the whole content has changed.
if (VNode1.tag !== VNode2.tag) {
// Just unmount the old node and mount the new node
mount(VNode2, element.parentNode)
unmount(Vnode1)
} else {
// Nodes have same tags
// So we have two checks remaining
// - Props
// - Children
// I am not going to check the props for now because it would just miss the point. I might write a third article which contains the full implementation
// Checking the children
// If the new node has a string for children
if (typeof VNode2.children == "string") {
// If the two children are **strictly** different
if (VNode2.children !== VNode1.children) {
element.textContent = VNode2.children;
}
} else {
// If the new node has an array of children
// - The length of children is the same
// - The old node has more children than the new one
// - The new node has more children than the old one
// Find out the lengths
const children1 = VNode1.children;
const children2 = VNode2.children;
const commonLen = Math.min(children1.length, children2.length)
// Recursively call patch for all the common children
for (let i = 0; i < commonLen; i++) {
patch(children1[i], children2[i])
}
// If the new node has fewer children
if (children1.length > children2.length) {
children1.slice(children2.length).forEach(child => {
unmount(child)
})
}
// If the new node has more children
if (children2.length > children1.length) {
children2.slice(children1.length).forEach(child => {
mount(child, element)
})
}
}
}
}
// END OF VDOM ENGINE
//***********************************************************************************
function generateList(list) {
let children = list.map(child => createVNode("li", null, child));
return createVNode("ul", { class: 'fruits-ul' }, children)
}
let list = generateList(["apple", "banana", "orange"]);
mount(list, document.querySelector("#app"))
// Later
//****************
setTimeout(() => patch(list, generateList(["apple", "banana", "pineapple changed"])), 3000)
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.