Skip to content

Instantly share code, notes, and snippets.

@IanSSenne
Created March 15, 2020 05:36
Show Gist options
  • Save IanSSenne/7030beac3b0770d2e8e6e4fbe9a08a0f to your computer and use it in GitHub Desktop.
Save IanSSenne/7030beac3b0770d2e8e6e4fbe9a08a0f to your computer and use it in GitHub Desktop.
export * from "./core";
export * from "./hook";
import { HOOK_ID } from "./hook";
let currentComponent: ComponentRoot[] = [];
export function getCurrentComponent(): ComponentRoot {
return currentComponent[currentComponent.length - 1];
}
enum CONST {
isComponent = "isComponent"
}
class HooksStoreInstance {
store: any[] = [];
}
function flat(arr: any): any[] {
const res = [];
for (let item of arr) {
if (Array.isArray(item)) {
res.push(...flat(item));
} else {
res.push(item);
}
}
return res;
}
class VirtualNode {
[CONST.isComponent] = false;
type: string;
props: object | null;
children: (string | VirtualNode | ComponentRoot)[];
constructor(
type: string,
props: null | object,
...children: (ComponentRoot | string | VirtualNode)[]
) {
this.type = type;
this.props = props;
this.children = flat(children);
}
render() {
const node: any = document.createElement(this.type);
if (this.props)
for (const prop in this.props) {
node[prop] = (<any>this.props)[prop];
}
for (const child of this.children) {
node.appendChild(this.resolveToDom(child));
}
return node;
}
resolveToDom(t: ComponentRoot | string | VirtualNode) {
if (typeof t != "object") {
return document.createTextNode(t);
} else if (t instanceof ComponentRoot || t instanceof VirtualNode) {
return t.render();
}
}
}
class HooksStore {
private hooks: HooksStoreInstance[] = [];
get(id: number) {
while (!this.hooks[id]) {
this.hooks.push(new HooksStoreInstance());
}
return this.hooks[id];
}
}
export class ComponentRoot {
props: Object | null;
children: any[];
component: Function;
parent: ComponentRoot | undefined;
lastRenderResult: any;
constructor(func: Function, props: null | Object, children: any[]) {
this.component = func;
this.props = props;
this.children = children;
}
makeDirty() {
if (this.bubble) {
this.parent?.makeDirty();
} else {
this.rerender();
}
}
rerender() {
const next = new ComponentRoot(this.component, this.props, this.children);
next.hooks
.get(HOOK_ID.useState)
.store.push(...this.hooks.get(HOOK_ID.useState).store);
currentComponent.push(next);
next.setChild(this.component(this.props));
if (!this.lastRenderResult) {
console.log("RERENDER CALLED UNEXPECTEDLY");
}
const nextRenderResult = next.render();
this.lastRenderResult.replaceWith(nextRenderResult);
this.lastRenderResult = nextRenderResult;
currentComponent.pop();
}
render(): any {
const result = this.childNode?.render();
this.lastRenderResult = result;
return result;
}
childNode: VirtualNode | ComponentRoot | undefined;
hooks: HooksStore = new HooksStore();
[CONST.isComponent] = true;
bubble = false;
setChild(childNode: VirtualNode | ComponentRoot) {
this.childNode = childNode;
if (childNode[CONST.isComponent]) {
(<ComponentRoot>this.childNode).bubble = true;
}
}
setParent(parent: ComponentRoot) {
this.parent = parent;
}
}
export const DOM = (a: any, b: any, ...c: any[]) => {
b = b || {};
if (typeof a == "function") {
currentComponent.push(new ComponentRoot(a, b, c));
let res = a({ ...Object(b || {}), children: c });
let component = currentComponent.pop();
component?.setChild(res);
return component;
} else {
return new VirtualNode(a, b, ...c.filter(Boolean));
}
};
export * from "./hooks-useState";
import { getCurrentComponent, ComponentRoot } from "../core";
export enum HOOK_ID {
useState
}
class State<T> {
value: T;
updater: (next: T) => void;
component: ComponentRoot;
toConsumable() {
return [this.value, this.updater];
}
constructor(initialValue: T, component: ComponentRoot) {
this.value = initialValue;
this.component = component;
this.updater = (next: T) => {
this.value = next;
this.component.makeDirty();
//dispatch rerender
};
}
}
export const useState: any = <T>(initialValue: T) => {
let component = getCurrentComponent();
if (component.hooks.get(HOOK_ID.useState).store[0]) {
const previousValue = getCurrentComponent()
.hooks.get(HOOK_ID.useState)
.store.shift();
return previousValue.toConsumable();
} else {
const state = new State<T>(initialValue, component);
component.hooks.get(HOOK_ID.useState).store.push(state);
return state.toConsumable();
}
};
import * as FW from "./all";
export default FW;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment