A lot of the react principle of the analytic such articles are now online, but often this article after I finish see no harvest, because writing ideas quickly, most is to write a few words of simple introduce this code is used to do, and then put a source let you see, probably the author himself is really understand, But for most people reading this article, it’s not.

Explain the source code of a framework, the best way is to achieve a simple version, so in the process of your implementation, the reader will be able to understand your overall thinking, also can stand on a higher level of the framework has an overall cognition, rather than stuck in some specific technical details.

This article is an excellent implementation of a simple React framework. The following is the translation of the original text and some of my own understanding in the process of using it.

We’ll implement a simple React with a simple component-level API and the virtual DOM. The article will also be divided into four parts

  • Elements: In this chapter we will learn how JSX is treated as a virtual DOM
  • In this section we will show you how to Rendering the virtual DOM into the real DOM
  • Patching: In this chapter we will show you why keys are so important and how to use the virtual DOM to batch update existing DOM
  • Components: The last section tells you about the React component and its life cycle

Element

The element carries a lot of important information, such as the type, props and children list of the node. According to these attributes, we can render the element we need. Its tree structure is as follows

{
    "type": "ul"."props": {
        "className": "some-list"
    },
    "children": [{"type": "li"."props": {
                "className": "some-list__item"
            },
            "children": [
                "One"] {},"type": "li"."props": {
                "className": "some-list__item"
            },
            "children": [
                "Two"]]}}Copy the code

But if we were writing everyday code we would be crazy to write it like this, so we would write JSX syntax

/** @jsx createElement */
const list = <ul className="some-list">
    <li className="some-list__item">One</li>
    <li className="some-list__item">Two</li>
</ul>;
Copy the code

In order for it to be compiled as a regular method, we need to annotate it to define which function to use, which function is executed, and which is eventually returned to a virtual DOM

const createElement = (type, props, ... children) => { props = props ! = null ? props : {};return {type, props, children};
};
Copy the code

React is not defined when babelrc is configured with CreateElement (CreateElement). However, I installed the author’s simple React class package and later learned that JSX requires a comment to tell Babel which function to use when compiling

/** @jsx Gooact.createElement */
Copy the code

Rendering

This section is about rendering the VDOM to the real DOM

In the previous section, we had a virtual DOM tree structure derived from JSX syntax, so it was time to render this virtual DOM structure into the real DOM

So we get a tree structure, how to determine the true dom node should be rendered into what look like, here there will be three kinds of circumstances, the first is direct returns a string, then we can directly generate a text node, if the return is a our custom components, so we call this method, If it is a regular DOM component, we create such a DOM element, and then continue iterating through its children.

SetAttribute sets the properties that we set on the virtual DOM on the real DOM

const 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)) // flatten 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}.`);
    }
};

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 == 'id') {
        dom[key] = value;
    } else if (key == 'key') {
        dom.__gooactKey = value;
    } else if(typeof value ! ='object'&& typeof value ! ='function') { dom.setAttribute(key, value); }};Copy the code

Patching

Imagine a situation where you have a very deep structure, and you need to update your virtual DOM frequently. If you change something, then it all has to be rendered, which can take a lot of time.

But if we had an algorithm that could compare the differences between the new virtual DOM and the existing DOM, and then update only those changes, it would be the oft-cited React team that made some practical conventions that reduced the time complexity from O (n)^3 to O (n). These would be the two main conventions

  • Two elements with different types produce two different trees
  • When we give a key attribute, it will judge from it
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) {
        returndom.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 inArray.from(dom.childNodes)) { const child = dom.childNodes[index]; const key = child.__gooactKey || index; pool[key] = child; } const vchildren = [].concat(... vdom.children); // flattenfor (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();
        returndom; }};Copy the code

Component

A component is the closest thing to a function in JS. It shows what should be displayed on the screen. It can be defined as a stateless function, or as a component with a life cycle.Copy the code
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)) {returnpatch(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) {
        returnnextProps ! = 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() {
        returnundefined; }}Copy the code

This article concludes with the new Gooact wheel. Let’s see what it can do

  • It can update complex DOM structures efficiently
  • Both functional and state components are supported

How close is it to a full React app?

  • He also doesn’t support new versions of fragments and Portals
  • Because React Fiber is too complex, there is no support yet
  • If you write duplicate keys, there may be bugs
  • There are also some missing callbacks for some methods, but does this article give you a new perspective on the React framework and give you a global view of what it does? After reading the React framework, I get a clearer idea of how to use it. Finally, I present a use case demo using the framework