Skip to content

Instantly share code, notes, and snippets.

@mrchaofan
Created June 8, 2022 09:53
Show Gist options
  • Save mrchaofan/9e3a6d78229baf4ef6b6c3e263816aff to your computer and use it in GitHub Desktop.
Save mrchaofan/9e3a6d78229baf4ef6b6c3e263816aff to your computer and use it in GitHub Desktop.
function getSiblingHost(cCs: Array<Component>, index = 0): Node | void {
for (let j = index + 1; j < cCs.length; j++) {
const dom = cCs[j].hostElement;
if (dom) {
return dom;
}
}
}
function getParentDom(cCs: Component): Node {
while (cCs.parent) {
cCs = cCs.parent;
const dom = cCs.hostElement;
if (dom) {
return dom;
}
}
}
class Component {
public parent?: Component;
public hostElement?: Node;
public childComponents?: Array<Component>;
public elementDescriptor?: ElementDescriptor;
public mountIndex?: number;
public update(elementDescriptor: ElementDescriptor): Node {
const oldElementDescriptor = this.elementDescriptor;
if (!oldElementDescriptor) {
if (elementDescriptor.tag === "text") {
this.hostElement = document.createTextNode(
elementDescriptor.props.data
);
} else {
this.hostElement = document.createElement(elementDescriptor.tag);
}
} else {
if (elementDescriptor.tag === "text") {
this.hostElement.textContent = elementDescriptor.props.data;
}
}
this.elementDescriptor = elementDescriptor;
this.updateChildren(elementDescriptor.children);
return this.hostElement;
}
public isSameTag(elementDescriptor: ElementDescriptor): boolean {
return (
this.elementDescriptor &&
this.elementDescriptor.tag === elementDescriptor.tag &&
this.elementDescriptor.props.key === elementDescriptor.props.key
);
}
public updateChildren(elementDescriptors: Array<ElementDescriptor>) {
const childComponents = this.childComponents
? this.childComponents.slice()
: [];
const nextChildComponents = [];
const map = {};
for (let i = 0; i < childComponents.length; i++) {
const cC = childComponents[i];
cC.mountIndex = i;
if (cC.elementDescriptor.props.key) {
map[cC.elementDescriptor.props.key] = cC;
}
}
let placeIndex = 0;
for (let i = 0; i < elementDescriptors.length; i++) {
const eD = elementDescriptors[i];
let cC: Component;
if (eD.props.key) {
cC = map[eD.props.key];
}
if (!cC) {
for (let j = 0; j < childComponents.length; j++) {
if (
childComponents[j] &&
childComponents[j].elementDescriptor.props.key == null &&
childComponents[j].isSameTag(eD)
) {
cC = childComponents[j];
break;
}
}
}
let newDom: Node;
if (cC) {
childComponents[cC.mountIndex] = null;
if (cC.mountIndex > placeIndex) {
placeIndex = cC.mountIndex;
}
newDom = cC.update(eD);
} else {
cC = new Component();
cC.parent = this;
newDom = cC.update(eD);
}
nextChildComponents.push(cC);
if (cC.mountIndex == null || cC.mountIndex < placeIndex) {
const parentDom = getParentDom(cC) as Node;
const dom = getSiblingHost(this.childComponents || [], placeIndex);
if (dom) {
parentDom.insertBefore(newDom, dom);
} else {
parentDom.appendChild(newDom);
}
}
}
for (let i = 0; i < childComponents.length; i++) {
if (childComponents[i]) {
childComponents[i].destroy();
}
}
this.childComponents = nextChildComponents;
}
public destroy() {
for (let i = 0; i < this.childComponents.length; i++) {
this.childComponents[i].destroy();
}
if (this.hostElement && this.hostElement.parentNode) {
this.hostElement.parentNode.removeChild(this.hostElement);
}
}
}
type PropType<T> = T extends "text"
? {
data: string;
key?: string | number;
}
: Record<keyof any, any> & { key?: string | number };
type ChildType<T> = T extends "text" ? Array<never> : Array<ElementDescriptor>;
class ElementDescriptor<T extends string = string> {
public children: ChildType<T>;
constructor(
public tag: T,
public props: PropType<T>,
...children: ChildType<T>
) {
this.children = children;
}
}
const ui1 = new ElementDescriptor(
"div",
{},
new ElementDescriptor("text", {
data: "AMAZING JavaScript"
}),
new ElementDescriptor(
"div",
{},
new ElementDescriptor("text", {
data: "World "
}),
new ElementDescriptor("text", {
data: "Hello "
})
),
new ElementDescriptor(
"span",
{},
new ElementDescriptor("text", {
data: "span"
})
)
);
const ui2 = new ElementDescriptor(
"div",
{},
new ElementDescriptor(
"div",
{},
new ElementDescriptor("text", {
data: "Hello "
}),
new ElementDescriptor("text", {
data: "World"
})
),
new ElementDescriptor("text", {
data: "AMAZING JavaScript"
}),
new ElementDescriptor(
"div",
{},
new ElementDescriptor("text", {
data: "bonjour/coucou/salut le monde"
})
)
);
const ui3 = new ElementDescriptor(
"div",
{},
new ElementDescriptor("text", {
data: "Hello "
}),
new ElementDescriptor("text", {
data: "World "
})
);
const ui4 = new ElementDescriptor(
"div",
{},
new ElementDescriptor("text", {
data: "World "
}),
new ElementDescriptor("text", {
data: "Hello "
})
);
const containerComponent = new Component();
containerComponent.hostElement = document.body;
containerComponent.updateChildren([ui1]);
setTimeout(() => {
containerComponent.updateChildren([ui2]);
}, 3000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment