React Code Reading #1

React Code Reading

1st day. Read Preact h function. h is hyperscript which generate HyperText from JavaScript code. It's different from Shadow DOM. Shadow dom enables an encapsulated scope of styles. Styles declared within shadow DOM only applies the DOMs under shadow-root.

Virtual DOM is a JavaScript object having the information of real DOM tree. For example, VDOM equivalent to the DOM below needs to know three things.

<div id="root">Hello, world!!</div>
  1. The DOM root is div tag.
  2. The div tag has id of "root"
  3. The div tag has string "Hello, world!!" as a child.

Following JavaScript object describes these information

const rootNode = {
  tagName: 'div',
  attributes: {
    id: 'root'
  },
  children: ['Hello, world!!']
}

This object knows everything to render above HTML. hyperscript consumes virtualDOM object and produces an actual html.


Reading h function of preact. h function of preact consumes root VNode (node of Virtual DOM) and produces whole VDOM tree. In React or other similar view libraries, they let us make Components to reuse and encapsulate single VNode. For example, the Button function below can be used anywhere.

const Button = (attributes, children) => ({
  tagName: 'button',
  attributes: attributes,
  children: children
})
const rootNode = {
  tagName: 'div',
  attributes: {
    id: 'root'
  },
  children: ['Hello, world!!', Button({className: "primary-button"}, "Click me!")]
}

For the sake of ease, we use JSX to write Components in the same manner as HTML. Using JSX, the above code can be written like this.

const rootNode = <div>
  Hello, world!!
  <Button className="primary-button">Click me!</Button>
</div>

When React renders the VDOM tree in real DOM, they need to expand all those Components until all VNode are expressed in primitive DOM types provided by browsers(ex. div, a, p, etc). In preact h function, it actually does not produce HTML string but produces VDOM used in preact.


Let's dive into the real code.

import { VNode } from './vnode';
import options from './options';

const stack = [];

const EMPTY_CHILDREN = [];

export function h(nodeName, attributes) {
    let children=EMPTY_CHILDREN, lastSimple, child, simple, i;
    for (i=arguments.length; i-- > 2; ) {
        stack.push(arguments[i]);
    }
    if (attributes && attributes.children!=null) {
        if (!stack.length) stack.push(attributes.children);
        delete attributes.children;
    }
    while (stack.length) {
        if ((child = stack.pop()) && child.pop!==undefined) {
            for (i=child.length; i--; ) stack.push(child[i]);
        }
        else {
            if (typeof child==='boolean') child = null;

            if ((simple = typeof nodeName!=='function')) {
                if (child==null) child = '';
                else if (typeof child==='number') child = String(child);
                else if (typeof child!=='string') simple = false;
            }

            if (simple && lastSimple) {
                children[children.length-1] += child;
            }
            else if (children===EMPTY_CHILDREN) {
                children = [child];
            }
            else {
                children.push(child);
            }

            lastSimple = simple;
        }
    }

    let p = new VNode();
    p.nodeName = nodeName;
    p.children = children;
    p.attributes = attributes==null ? undefined : attributes;
    p.key = attributes==null ? undefined : attributes.key;

    // if a "vnode hook" is defined, pass every created VNode to it
    if (options.vnode!==undefined) options.vnode(p);

    return p;
}

This is the real code of h function in preact. It does quite simple stuff. It just consumes the Object provided by user and returns the VNode object. Simple is that.


References
React Components, Elements, and Instances
Reconciliation
仮想DOMの内部の動き
virtual-dom