Skip to content

Instantly share code, notes, and snippets.

@taylorlapeyre
Last active July 23, 2018 17:46
Show Gist options
  • Save taylorlapeyre/e47883ea405b3056a133c00a6642ca12 to your computer and use it in GitHub Desktop.
Save taylorlapeyre/e47883ea405b3056a133c00a6642ca12 to your computer and use it in GitHub Desktop.
React if jsx was actually just arrays like ["h1", "Hello World"]
const Ops = {
CREATE: "CREATE",
UPDATE: "UPDATE",
DELETE: "DELETE",
KEEP: "KEEP"
};
function constructTree(dom) {
if (dom.nodeType === document.TEXT_NODE) {
return dom.textContent;
} else {
const tag = dom.tagName.toLowerCase();
const body = dom.childNodes;
const children = [];
for (const child of body) {
children.push(constructTree(child));
}
return [tag, ...children];
}
}
function reconcile(newTree, existingTree) {
if (!newTree && existingTree) {
return {
op: Ops.DELETE
};
} else if (newTree && !existingTree) {
if (Array.isArray(newTree)) {
return {
op: Ops.CREATE,
type: newTree[0],
children: newTree
.slice(1)
.map(newChild => reconcile(newChild, undefined))
};
} else {
return {
op: Ops.CREATE,
type: "TEXT",
children: newTree
};
}
} else {
if (Array.isArray(newTree) && !Array.isArray(existingTree)) {
return {
op: Ops.UPDATE,
type: newTree[0],
children: newTree
.slice(1)
.map(newChild => reconcile(newChild, undefined))
};
} else if (!Array.isArray(newTree)) {
return {
op: Ops.UPDATE,
type: "TEXT",
children: newTree
};
} else {
const [newType, ...newChildren] = newTree;
const [existingType, ...existingChildren] = existingTree;
const pairedChildren = Array(
Math.max(newChildren.length, existingChildren.length)
)
.fill()
.map((_, index) => {
const existingChild =
index < existingChildren.length
? existingChildren[index]
: undefined;
const newChild =
index < newChildren.length ? newChildren[index] : undefined;
return { newChild, existingChild };
});
if (newType !== existingType) {
return {
op: Ops.UPDATE,
type: newType,
children: pairedChildren.map(({ newChild, existingChild }) =>
reconcile(newChild, existingChild)
)
};
} else {
return {
op: Ops.KEEP,
type: newType,
children: pairedChildren.map(({ newChild, existingChild }) =>
reconcile(newChild, existingChild)
)
};
}
}
}
}
function commit(operation, dom) {
if (operation.op === Ops.CREATE) {
let element;
if (operation.type === "TEXT") {
element = document.createTextNode(operation.children);
} else {
element = document.createElement(operation.type);
for (const nextOp of operation.children) {
commit(nextOp, element);
}
}
return element;
}
if (operation.op === Ops.UPDATE) {
if (operation.type === "TEXT") {
return document.createTextNode(operation.children);
} else {
const childNodes = dom.childNodes;
dom.replaceWith(document.createElement(operation.type));
for (const [index, nextOp] of operation.children.entries()) {
const child = commit(nextOp, childNodes[index]);
console.log(child);
if (child) {
dom.appendChild(child);
}
}
}
return dom;
}
switch (operation.op) {
case Ops.DELETE: {
return null;
}
case Ops.KEEP: {
const childNodes = Array.from(dom.childNodes);
for (const [index, nextOp] of operation.children.entries()) {
const child = commit(nextOp, childNodes[index]);
if (childNodes[index]) {
dom.replaceChild(child, childNodes[index]);
} else if (child) {
dom.appendChild(child);
}
}
return dom;
}
}
}
function render(newTree, container) {
const existingTree = constructTree(container);
const operation = reconcile(newTree, existingTree);
commit(operation, container);
}
render(
[
"section",
["div", ["p", "Information bout"]],
["h1", "hii"],
["h2", "I'm subtitle"]
],
document.getElementById("root")
);
render(
[
"section",
["div", ["p", "Information bout"]],
["h1", "byee"],
["h2", "I'm subtitle"]
],
document.getElementById("root")
);
window.render = render;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment