Skip to content

Instantly share code, notes, and snippets.

@s-melnikov
Created May 10, 2018 10:24
Show Gist options
  • Save s-melnikov/7c585c47550ff4e00ce9335ab55e6b83 to your computer and use it in GitHub Desktop.
Save s-melnikov/7c585c47550ff4e00ce9335ab55e6b83 to your computer and use it in GitHub Desktop.
function h(type, props, ...children) {
props = props != null ? props : {};
return { type, props, children };
}
function render(vdom, parent = null) {
if (parent) parent.textContent = "";
const mount = parent ? (el => parent.appendChild(el)) : (el => el);
if (typeof vdom == "string" || typeof vdom == "number") {
return mount(document.createTextNode(vdom));
} else if (typeof vdom == "boolean" || vdom === null) {
return mount(document.createTextNode(''));
} else if (typeof vdom == "object" && typeof vdom.type == "function") {
return mount(Component.render(vdom));
} else if (typeof vdom == "object" && typeof vdom.type == "string") {
const dom = document.createElement(vdom.type);
for (const child of [].concat(...vdom.children)) {
dom.appendChild(render(child));
}
for (const prop in vdom.props) {
setAttribute(dom, prop, vdom.props[prop]);
}
return mount(dom);
} else {
throw new Error(`Invalid VDOM: ${vdom}.`);
}
}
function setAttribute(dom, key, value) {
if (typeof value == "function" && key.startsWith('on')) {
const eventType = key.slice(2).toLowerCase();
dom.__gooactHandlers = dom.__gooactHandlers || {};
dom.removeEventListener(eventType, dom.__gooactHandlers[eventType]);
dom.__gooactHandlers[eventType] = value;
dom.addEventListener(eventType, dom.__gooactHandlers[eventType]);
} else if (key == "checked" || key == "value" || key == "id") {
dom[key] = value;
} else if (key == "key") {
dom.__gooactKey = value;
} else if (typeof value != "object" && typeof value != "function") {
dom.setAttribute(key, value);
}
};
function patch(dom, vdom, parent = dom.parentNode) {
const replace = parent ? el => (parent.replaceChild(el, dom) && el) : (el => el);
if (typeof vdom == "object" && typeof vdom.type == "function") {
return Component.patch(dom, vdom, parent);
} else if (typeof vdom != "object" && dom instanceof Text) {
return dom.textContent != vdom ? replace(render(vdom)) : dom;
} else if (typeof vdom == "object" && dom instanceof Text) {
return replace(render(vdom));
} else if (typeof vdom == "object" && dom.nodeName != vdom.type.toUpperCase()) {
return replace(render(vdom));
} else if (typeof vdom == "object" && dom.nodeName == vdom.type.toUpperCase()) {
const pool = {};
const active = document.activeElement;
for (const index in Array.from(dom.childNodes)) {
const child = dom.childNodes[index];
const key = child.__gooactKey || index;
pool[key] = child;
}
const vchildren = [].concat(...vdom.children);
for (const index in vchildren) {
const child = vchildren[index];
const key = child.props && child.props.key || index;
dom.appendChild(pool[key] ? patch(pool[key], child) : render(child));
delete pool[key];
}
for (const key in pool) {
if (pool[key].__gooactInstance)
pool[key].__gooactInstance.componentWillUnmount();
pool[key].remove();
}
for (const attr of dom.attributes) dom.removeAttribute(attr.name);
for (const prop in vdom.props) setAttribute(dom, prop, vdom.props[prop]);
active.focus();
return dom;
}
}
class Component {
constructor(props) {
this.props = props || {};
this.state = null;
}
static render(vdom, parent = null) {
const props = Object.assign({}, vdom.props, { children: vdom.children });
if (Component.isPrototypeOf(vdom.type)) {
const instance = new (vdom.type)(props);
instance.componentWillMount();
instance.base = render(instance.render(), parent);
instance.base.__gooactInstance = instance;
instance.base.__gooactKey = vdom.props.key;
instance.componentDidMount();
return instance.base;
} else {
return render(vdom.type(props), parent);
}
}
static patch(dom, vdom, parent = dom.parentNode) {
const props = Object.assign({}, vdom.props, {children: vdom.children});
if (dom.__gooactInstance && dom.__gooactInstance.constructor == vdom.type) {
dom.__gooactInstance.componentWillReceiveProps(props);
dom.__gooactInstance.props = props;
return patch(dom, dom.__gooactInstance.render());
} else if (Component.isPrototypeOf(vdom.type)) {
const ndom = Component.render(vdom);
return parent ? (parent.replaceChild(ndom, dom) && ndom) : (ndom);
} else if (!Component.isPrototypeOf(vdom.type)) {
return patch(dom, vdom.type(props));
}
}
setState(nextState) {
if (this.base && this.shouldComponentUpdate(this.props, nextState)) {
const prevState = this.state;
this.componentWillUpdate(this.props, nextState);
this.state = nextState;
patch(this.base, this.render());
this.componentDidUpdate(this.props, prevState);
} else {
this.state = nextState;
}
}
shouldComponentUpdate(nextProps, nextState) {
return nextProps != this.props || nextState != this.state;
}
componentWillReceiveProps(nextProps) {
return undefined;
}
componentWillUpdate(nextProps, nextState) {
return undefined;
}
componentDidUpdate(prevProps, prevState) {
return undefined;
}
componentWillMount() {
return undefined;
}
componentDidMount() {
return undefined;
}
componentWillUnmount() {
return undefined;
}
}
//example
class Main extends Component {
render() {
return h("div", { class: "main" }, "Hello world!");
}
}
render(h(Main), document.body);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment