Created
June 8, 2022 09:53
-
-
Save mrchaofan/9e3a6d78229baf4ef6b6c3e263816aff to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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