Gooact: React in 160 lines of JavaScript
Published: 2020-10-09 21:34:57

Awesome !!!

Article : [Gooact](https://medium.com/@sweetpalma/gooact-react-in-160-lines-of-javascript-44e0742ad60f)

You can test it here: [TestGooact](https://flems.io/#0=N4IgtglgJlA2CmIBcBWAHAOgJwCYA0IAZhAgM7IDaoAdgIZiJIgYAWALmLCAQMYD21NvEHIQAHigQAbgAJoAXgA6IAE58+bZQD4xAeklSt3EKXgIebCAPJMADEhwBGEAF88NeoxABzdbQsYAFbkvAJCIky6AFQyAOJ+FjIARgCeMgDKAO7w8GwACrSwYLR4Mji2jpgyAIKwsDIqEN7spA3wpipS8FAYMlG6itQAFEMAlDLyWjLAMgDkAK6mMqRsjRazANyDg-zUKzI8KvC0QgCiCAyCEzJDbCkADvCl92r3pKUYnzwsJFBH1ONJtNBjJQXJCDcXnw3hN5PIZNR5nVxlCYfDgC4ttQwW02PMVNjgHdHs9Xu8Dj9YH9hJjBrTqDtrGxlrlqmxVhAkvMhNchlA+GBSgBreApUpSQrzeCAqbAEFgiAQ24PeB8CES2BS2FzQjzagWKzUWYyABkJpkIpSGBWtBUbFIAHUIGwWENZgJZqNxnLsTiwbt9vAuoIACoq66W62wCA8eBDHCjDBsPgAGT42RUAGFaKYxli-WD+WAMAB9Eu+Pj+NgACVo1Dg8BUrXhRdL5YSNbrDabMgAPr3pvSC6DW0cwHwuqdg2wUxAVsJG0Mg8I2GGSTJW2WK1Xa-WEE2KMvQyqALqjfPDzftysWXfd0iH6dr+An64aqUXgut2gwKcr2fztQi5HquKqlFe263l2+4PiBz5np+MguDIZhLIqNyWtqszfPAPAilAxr9haopYe+8CEQOmFwnMPCwDmpAAHKeJ6wK+l+AoUJar7wmRiHIah8DghhJHUbMKwpAgxpmjIxKquqkqCaJfBJIEuFsCxPrDjIADyKlqRg9FNMMrbiQg4oKee8qgvxsBoUqVHwrMRyEFJ5qyWqMhkVhur6pYHrelZfpkXyAqWWxSEobZgnoUMDlzJaGmBTiEEdgA0iJnkKXxkV2Tc7nyZqgkAISOcpqnrKabkqh5XklTqeoGv5rFaSZrLso0XJCLFormYVYU4i4dJYoyezMv8UCNryUhFs8torvIiLIhMsqBQGzLjnqzLwvcc1XAA-DcZjLTIO3-GwBn3I89aZpSUBLrAXoyEgh31ECZj9QqSr5Z5RZYSsjTUN4FEydV6q-aJiJgEkjaJeFOJHHiBIyBtgghTw8yXOdhzHEIIbwAAHmwDF8BNQzTaFH3WTl0VfaDP0ClhSTqAgdbA+TYCwvCi0Pc1w4I-i2Io2waMYyuGDYyc8B44TxOk7MnqUxFAlCcqjw1eDpV6RV0nfezSbhqJPmNUaAVw2C-NI5mAr3AIYvjYu7OzWdis2blqtyfTHNKVr6mVSDatgwK+uPH9HKA7DWlrRuDPwkLIuY+LRyS+c8CY2TrayV6iE4oQfAqDcUffL8MgeRQJ7iwIPAnEMnwYHrRdUv8j32yoQwN1A4GhdnYK5-nbdMidrxyNieuoqQ4ymGwbIcp1cYzYP0Liq2Y8UKiCFJebuQC9HYDZcrmnDi6aiZAi8An6cKhqK3AAGACS1AatAMgAGoACLaQAss9AAkwDsy4GBr4uyGtsfUA8dpsG+LyeejsTq7TYPIZe8DZbSmOgfUEUcjj3DorGa4p0VwyAOkdIEQx8GCAwFgnB8Abq-Hup3MA4xpLvSei9Y671EIxV1hrOYZU1KuX9h7PWslvINT8ibXmBYLbYitmAG2QFyEQO+CFQUnsnYrhdtTFWXCGZ1XdD7fhv0ICjTrLGDy0s2Cmy0lIneSYCZsCtoIAhdV2aELaNg-wcYW7pwFGowQj1npFj3lFLRdMXHe3Kr7aShjjH6g9uYyxfMt5I0oR4oYXjYFkIsRo5WnDQncL0REgxQdqAk3gExBgMhnEZxVEmPgABVS6jZsy5keugv01iUmxjScICarcMnwKzoFV2NM8p5JjprQpftWwlImuUxSPFqmPFqQ0x4WYcxxlaRvDB4DmbXAxN3bZo0ZBVmkIpaO6ME4nMnBcFcByZBlwrvqautdWztxQePDAxR7gjHbqUIxE18YygkVpA4A8qIUl+G2SCbB0ppCItfMs-yCYll-ki-GLhr53JxDbPgsBOKim4hCqkdyXCKxxA83YVdha13rrdJunzaDfLbrdP59YCZAraZHMFGV24YDHn7XlY8MCYXhYitl+MUXADRRirFhYg6MqulAGhVJSHM3xSkV8B1FGuhxXiripR27jGel435O9Blmy-GYXIgldXqpPCSslPc84F25WkIxJ1mYJJBaCo5RibSxLwWqriUKOz339bGWVoIYp+rYCY1BMa40V1kbbQQTo6h1OoHHR1BZbXBrHBODZJKtkyF7i6o5JxVglwhN+dqnJuTtHGKOVOBbp4dXrUMCtKgMB0AYNmktzr+5HNRMPT2fKyQTzajPdt89URLyDivNefarnwAwLndGpA8zFusYEoZICGT6joqQVoMi5EELaWtFQ8wLB51IeO4FOIXRzjHdCZsC8YREX2cWx9pBrSxp5FzJEsA+KgJxDaSwPA2hsr6TO+BC1ANer9FHfl8JdIRIMkeoyQwMRzuLGPUowB27-GerS34-xSV3JiielN505x5DUMmWS2lCDeOLJnBDXLfUxNwVzM+NwhEqlGLel9facQJtiUm09qaSCwA-nwTam6LV+jE7GDASR1nXC8cpldXjRi+IsZG4e4aV1qdMCGm8bAw2xoDfCLTBmtOqfWWZqssK3zLzJMK0UdmuMrv4Mm+RbBX7QFk-JkTm9EbYnsyZ+AJLNGcsSeFyDvSWPBzjGPXTcDnZFvCoNfdoG-0xjgZA10MCYNnUQfO5BpT2P+nAWSa4qH9KGW8MMbDo68PTEI8IYjry6U0j7TFFK5nLNxqmUHLcobvOPP+le5M+dqL8ceNV9ixZxtDcm75yTbA02wAAEq4XgKcujL6hNvFCyOMb14qzDfE8ht9pADPWO1co+hTmLDXZUzpvTfbhkqyo-5jAtH6MaBVEx5LbH73DijvWGOMg-t2x6Q7Ur6iHtJOxJk1xpD4EUPgO42Myq7rQ5UUWRh5pCdGpuGTmLOSlRFTh+QwHGhgePFBwt6US32mo8K0okrQdZInfHl9rLA0QNgknukP9cYgKE3Z4Xa2JxeS0CBd9b9v7JZYV4drKqAdjnq-0RRr6Pwf1Rb9ir0gLA5NUjp2wBpUBJa3EN8+t4pQpdZIhwWJDRwpDi7V-CU3EuDMq429R7bNu7cq-ay7s7MkHdgbORt6ukfXENYCE1lrbho9Ptj87ux5OXcGaeyrqLpQVc6aj4H621HAtQFD11cPZJSRBm90Ib7sXi0PpjxL648fhaJ4Osn86qesPp795LbP0uWF5+LTl4XuXRfm6REqiv-ma+S7sUdp3p9CZN9QXF0E1iXfr9aHVOvL6+wDhd9vypvuO+S2A7P7ZfmVzbb27GQ7ZIhgH7vbv3E289QTWIEBFAHfqtEvk-tJivh-mvvXpvmwNvuztYn-vAAAd0MAeFEHv5lXhAS8EGIfg3l7hLvAZzogcgUAbuvfqCo-lJnUMFqjIQQlsQUYigWQSAZQQFkFnJrQW7j-kjAwYAagTiOgWAempmhwcLHQb-myiQXfvSIMLktruOFAEiMVI5Lwd0CxAoUoRgATDbHaK+gRknGcDcoIKUC3KUFbvSHIR7BoQgFhKoQROMJkP8umBgPEOZqCOiBLIYanCuCYQjioGYaAYIPSKSnmMYKYOYGIjYCAI4CgAAMwOAoCuDuAgA9peBBAhAgC7DhBsCiDRAxAAACwQ+McQHYicOM8AKcmMfQAw+6UcwALcyE8Ijh0OmQLhHYgwXi8oEg0gJcB6MYQoeyYwx0hQjYwsyg1YZgsAfAMgr8QYMgiwjYygowLgWggUExdQ0xDoecVIRUXR+g0gWgncFyYs3guQlRK4AAQikLfHdE5OoOpKMIMOeMYBtiQI2KIGptDFwAQOEWpIaFEQAOwOBJEeAMCiDixHovFhAriiCuAngECpGiCuFViuBAA)



Code:
```js
/* Gooact by SweetPalma, 2018. All rights reserved. */
(() => { 'use strict';

const createElement = (type, props, ...children) => {
    if (props === null) props = {};
    return {type, props, children};
};

const 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 == 'className') {
        dom[key] = value;
    } else if (key == 'style' && typeof value == 'object') {
        Object.assign(dom.style, value);
    } else if (key == 'ref' && typeof value == 'function') {
        value(dom);
    } else if (key == 'key') {
        dom.__gooactKey = value;
    } else if (typeof value != 'object' && typeof value != 'function') {
        dom.setAttribute(key, value);
    }
};

const render = (vdom, parent=null) => {
    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 Component.render(vdom, parent);
    } else if (typeof vdom == 'object' && typeof vdom.type == 'string') {
        const dom = mount(document.createElement(vdom.type));
        for (const child of [].concat(...vdom.children)) render(child, dom);
        for (const prop in vdom.props) setAttribute(dom, prop, vdom.props[prop]);
        return dom;
    } else {
        throw new Error(`Invalid VDOM: ${vdom}.`);
    }
};

const 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, parent)) : dom;
    } else if (typeof vdom == 'object' && dom instanceof Text) {
        return replace(render(vdom, parent));
    } else if (typeof vdom == 'object' && dom.nodeName != vdom.type.toUpperCase()) {
        return replace(render(vdom, parent));
    } else if (typeof vdom == 'object' && dom.nodeName == vdom.type.toUpperCase()) {
        const pool = {};
        const active = document.activeElement;
        [].concat(...dom.childNodes).map((child, index) => {
            const key = child.__gooactKey || `__index_${index}`;
            pool[key] = child;
        });
        [].concat(...vdom.children).map((child, index) => {
            const key = child.props && child.props.key || `__index_${index}`;
            dom.appendChild(pool[key] ? patch(pool[key], child) : render(child, dom));
            delete pool[key];
        });
        for (const key in pool) {
            const instance = pool[key].__gooactInstance;
            if (instance) instance.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(), parent);
        } else if (Component.isPrototypeOf(vdom.type)) {
            const ndom = Component.render(vdom, parent);
            return parent ? (parent.replaceChild(ndom, dom) && ndom) : (ndom);
        } else if (!Component.isPrototypeOf(vdom.type)) {
            return patch(dom, vdom.type(props), parent);
        }
    }

    setState(next) {
        const compat = (a) => typeof this.state == 'object' && typeof a == 'object';
        if (this.base && this.shouldComponentUpdate(this.props, next)) {
            const prevState = this.state;
            this.componentWillUpdate(this.props, next);
            this.state = compat(next) ? Object.assign({}, this.state, next) : next;
            patch(this.base, this.render());
            this.componentDidUpdate(this.props, prevState);
        } else {
            this.state = compat(next) ? Object.assign({}, this.state, next) : next;
        }
    }

    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;
    }
};

if (typeof module != 'undefined') module.exports = {createElement, render, Component};
if (typeof module == 'undefined') window.Gooact  = {createElement, render, Component};
})();
```