React source code analysis - first rendering (custom component) II

  • Preface

In the previous article, we explained that after the reactcomponentcomponent [ins] is initialized, the render method of App[ins] is called to generate the ReactElement tree, and then the corresponding ReactDOMComponent[6] is returned. Let's see how the ReactDOMComponent[6] is converted into a DOM tree.

performInitialMount: function (renderedElement, hostParent,
    hostContainerInfo, transaction, context) {
    ...
    
    // Here, the render method of App instance will be called, and the return value of render is the nested call of React.createElement.
    if (renderedElement === undefined) {
        renderedElement = this._renderValidatedComponent();
    }

    ...
    
    // Last time I talked about it
    // Return to ReactDOMComponent[6]
    var child = this._instantiateReactComponent(
        renderedElement,
        nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
    );
    
    this._renderedComponent = child;

    // Let's talk about this part today
    var markup = ReactReconciler.mountComponent(
        child,
        transaction,
        hostParent,
        hostContainerInfo,
        this._processChildContext(context),
        debugID
    );

    return markup;
},
  • ReactDOMComponent[6].mountComponent

ReactReconciler.mountComponent will trigger the mountComponent method of ReactDOMComponent[6], and the call stack is as follows:

...
|~mountComponentIntoNode()                                    |
  |-ReactReconciler.mountComponent()                          |
    |-ReactCompositeComponent[T].mountComponent()             |
      |-ReactCompositeComponent[T].performInitialMount()  upper half
        |-ReactReconciler.mountComponent()                    |
          |-ReactCompositeComponent[ins].mountComponent()     |
            |-this.performInitialMount()                      |
              |-this._renderValidatedComponent()              |
              |-instantiateReactComponent()                  _|_ 
                (we are here)                                 |
              |-ReactDOMComponent[6].mountComponent(          |
                  transaction, // scr: -----> not of interest |
                  hostParent,  // scr: -----> null            |
                  hostContainerInfo,// scr:---------------------> ReactDOMContainerInfo[ins]                                lower half
                  context      // scr: -----> not of interest |
                )                                             |
...
mountComponent: function (
    transaction,
    hostParent,
    hostContainerInfo,
    context
) {
    ...
    
    var mountImage;
    if (transaction.useCreateElement) {
        var ownerDocument = hostContainerInfo._ownerDocument;
        
        ...
        
        // Create div element
        el = ownerDocument.createElement(this._currentElement.type);
 
        ...
        
        // Set attributes
        if (!this._hostParent) {
            DOMPropertyOperations.setAttributeForRoot(el);
        }
        
        // Set properties
        this._updateDOMProperties(null, props, transaction);
        
        // Construct DOM tree
        var lazyTree = DOMLazyTree(el);
        
        // Traverse child nodes and create DOM nodes
        this._createInitialChildren(transaction, props, context, lazyTree);
        
        mountImage = lazyTree;
    }
    
    ...

    return mountImage;
}

There are three parts to do here:

  1. Create DOM elements
  2. Set attributes and properties
  3. Traverse the child elements and repeat the above process

The data structure is as follows:

Flow chart:

  • _createInitialChildren traverses child nodes and creates DOM nodes

Let's take a look at the details of "createInitialChildren":

_createInitialChildren: function (transaction, props, context, lazyTree) {
    // Intentional use of != to avoid catching zero/false.
    var innerHTML = props.dangerouslySetInnerHTML;
    if (innerHTML != null) {
        if (innerHTML.__html != null) {
            DOMLazyTree.queueHTML(lazyTree, innerHTML.__html);
        }
    } else {
        // Returns true if string or number
        var contentToUse =
            CONTENT_TYPES[typeof props.children] ? props.children :
            null;
        var childrenToUse = contentToUse != null ? null : props.children;

        // Render string directly
        if (contentToUse != null) {
            // Avoid setting textContent when the text is empty. In IE11 setting
            // textContent on a text area will cause the placeholder to not
            // show within the textarea until it has been focused and blurred again.
            // https://github.com/facebook/react/issues/6731#issuecomment-254874553
            if (contentToUse !== '') {
                DOMLazyTree.queueText(lazyTree, contentToUse);
            }
        } 
        // Render child nodes
        else if (childrenToUse != null) {
            var mountImages = this.mountChildren(
                childrenToUse,
                transaction,
                context
            );
            for (var i = 0; i < mountImages.length; i++) {
                DOMLazyTree.queueChild(lazyTree, mountImages[i]);
            }
        }
    }
},

This part of the code is easy to understand. There are three branches:

  1. Set the dangerouslySetInnerHTML attribute to render HTML directly
  2. Child node type is string or number, rendering character
  3. In other cases, you need to convert ReactElement to ReactDOMComponent or ReactCompositeComponent for further rendering.

There is only one line of code for both queueText and queueChild of DOMLazyTree

function queueText(tree, text) {
  if (enableLazy) { // scr: NO, I mean, false
    ...
  } else {
    setTextContent(tree.node, text);
  }
}

var setTextContent = function (node, text) {
  if (text) {
    var firstChild = node.firstChild;

  if (firstChild && firstChild === node.lastChild && firstChild.nodeType === 3) { // scr: false
    ...
    }
  }
  node.textContent = text; // scr: the only effective line
};

function queueChild(parentTree, childTree) {
  if (enableLazy) { // scr: again, false
    ...
  } else {
    parentTree.node.appendChild(childTree.node);
  }
}

The call stack of mountChildren is as follows:

ReactDOMComponent[6].mountComponent()    <-------------------------|
    (we are here)                                                  |
  |-this._createInitialChildren()                                  |
  ?{1}                                                             |
    |-DOMLazyTree.queueText()                                      |
  ?{2}                                                             |
    |-this.mountChildren()        // scr: ---------------> 1)(a)   |
      |-this._reconcilerInstantiateChildren()                      |
        |-ReactChildReconciler.instantiateChildren()               |
          |-traverseAllChildren()                                  |
            |-traverseAllChildrenImpl()  <------|inner             |
              |↻traverseAllChildrenImpl() ------|recursion         |
                |-instantiateChild()                               |
                  |-instantiateReactComponent()                    |
      |↻ReactDOMComponent.mountComponent()      // scr: -> 1)(b)---|
    |↻DOMLazyTree.queueChild()    // scr: ---------------> 2)

The function call logic in the middle is very clear. Finally, it will go to traverseAllChildrenImpl:

function traverseAllChildrenImpl(
    children,
    nameSoFar,
    callback,
    traverseContext
) {
    var type = typeof children;

    if (type === 'undefined' || type === 'boolean') {
        // All of the above are perceived as null.
        children = null;
    }

    if (children === null ||
        type === 'string' ||
        type === 'number' ||
        // The following is inlined from ReactElement. This means we can optimize
        // some checks. React Fiber also inlines this logic for similar purposes.
        (type === 'object' && children.$$typeof === REACT_ELEMENT_TYPE)) {
        callback(
            traverseContext,
            children,
            // If it's the only child, treat the name as if it was wrapped in an array
            // so that it's consistent if the number of children grows.
            nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) :
            nameSoFar
        );
        return 1;
    }

    var child;
    var nextName;
    var subtreeCount = 0; // Count of children found in the current subtree.
    var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar +
        SUBSEPARATOR;

    if (Array.isArray(children)) {
        for (var i = 0; i < children.length; i++) {
            child = children[i];
            nextName = nextNamePrefix + getComponentKey(child, i);
            subtreeCount += traverseAllChildrenImpl(
                child,
                nextName,
                callback,
                traverseContext
            );
        }
    } else {
       ...
    }

    return subtreeCount;
}

The logic here is very simple. If children is not an array, call the callback function; if it is an array, continue to call itself, which is equivalent to depth first traversal. The callback function here is instantiateChild in ReactChildReconciler:

function instantiateChild(childInstances, child, name, selfDebugID) {
    ...
    
    if (child != null && keyUnique) {
        childInstances[name] = instantiateReactComponent(child, true);
    }
}

Here, we directly call the instantiaereactcomponent to create the ReactDOMComponent. The creation order of all reactdomcomponents is as follows:

ReactDOMComponent[6].mountComponent()
  |-this._createInitialChildren()
    |-this.mountChildren() 
...           |↻instantiateReactComponent()[4,5]
      |-ReactDOMComponent[5].mountComponent()
        |-this._createInitialChildren()
          |-node.textContent = text; // scr: [5] done
      |-ReactDOMComponent[4].mountComponent()
        |-this._createInitialChildren()
          |-this.mountChildren() 
...                 |↻instantiateReactComponent()[2,3]
          |-ReactDOMComponent[2].mountComponent() // scr: [2] done
          |-ReactDOMComponent[3].mountComponent()
            |-this._createInitialChildren()
              |-node.textContent = text; // scr: [3] done
        |↻node[4].appendChild()[2,3] // scr: [4] done

    |↻node[6].appendChild()[4,5] // scr: [6] done

Completed flowchart:

Keywords: Javascript React github Attribute

Added by westonlea7 on Wed, 11 Dec 2019 19:59:29 +0200