We have completed the rendering process of a simple component. This time we are going to explore more ramifications of this process by discussing how a class component (a typical one we might use in everyday development) is rendered.

Files used in this article:

the same as post one and two

I use {} to reference the previous post if the methods (or logic process) has been discussed in it.

The component named App is similar to what I gave in the beginning of post one. But since we have leveled-up a bit, it does not look that daunting anymore.

Import React, {Component} from 'React'; Import the logo from '. / logo. SVG '; The import '. / App. CSS '; class App extends Component { constructor(props) { super(props); this.state = { desc: 'start', }; } render() { return ( <div className="App"> <div className="App-header"> <img src="main.jpg" className="App-logo" alt="logo" /> <h1> "Welcom to React" </h1> </div> <p className="App-intro"> { this.state.desc } </p> </div> ); } } export default App; [email protected]Copy the code

As mentioned, the component above is rendered using:

Reactdom.render (<App />, document.getelementByid (' root '));Copy the code

Now the babeled code:

import React, { Component } from 'react'; import logo from './logo.svg'; import './App.css'; class App extends Component { constructor(props) { super(props); this.state = { desc: 'start', }; } render() { return React.createElement( 'div', { className: 'App' }, React.createElement( 'div', { className: 'App-header' }, React.createElement( 'img', { src: "main.jpg", className: 'App-logo', alt: 'logo' } ), React.createElement( 'h1', null, ' "Welcom to React" ' ) ), React.createElement( 'p', { className: 'App-intro' }, this.state.desc ) ); } } export default App; . ReactDOM.render(React.createElement(App, null), document.getElementById('root'));Copy the code

Here we consider Component a common base class, as other methods will not be used in this post.

This time we can fast forward the logic that is shared with simple component.

Construct the top level wrapper ReactCompositeComponent[T]

The designated data structure:

This step is almost the same as that in simple component rendering, so I will give a brief description only, it

1) creates ReactElement[1] using ReactElement.createElement(type, config, children) (This time App is passed to type, and config, children are null);

  1. creates ReactElement[2] in _renderSubtreeIntoContainer();

  2. create the designated wrapper with instantiateReactComponent().

ReactElement.createElement(type,    // scr: -------------> App
                           config,  // scr: -------------> null
                           children // scr: -------------> null
) // scr: ------------------------------------------------------> 1)Copy the code

ReactDOM.render |=ReactMount.render(nextElement, container, callback) |=ReactMount._renderSubtreeIntoContainer( parentComponent, // scr: ----> null nextElement, // scr: ----> ReactElement[1] container, // SCR: ----> document.getElementById(' root ') callback '// SCR: ----> undefined ) // scr: ------------------------------------------------------> 2) |-instantiateReactComponent( // scr: -------------------------> 3) node, // scr: ------> ReactElement[2] shouldHaveDebugID /* false */ ) |-ReactCompositeComponentWrapper( element // scr: ------> ReactElement[2] ); |=ReactCompositeComponent.construct(element /* same */)Copy the code

This is what we covered in {post one}.

Initialize ReactCompositeComponent[T]

The designated data structure:

The step is the same as well:

1) ReactDOMContainerInfo[ins] Represents the container DOM element, document.getelementByID (‘ root ‘);

2) TopLevelWrapper is instantiated (TopLevelWrapper[ins]) and is set to ReactCompositeComponent[T]._currentElement alongside the initialization of other properties;

3) Again, mountComponentIntoNode is the cross point of upper and lower half, within which ReactCompositeComponent[T].mountComponent returns a complete DOMLazyTree that can be used by ReactMount._mountImageIntoNode, a method from lower half.

ReactDOM.render ___ |=ReactMount.render(nextElement, container, callback) | |=ReactMount._renderSubtreeIntoContainer() | |-ReactMount._renderNewRootComponent() | |-instantiateReactComponent() | |~batchedMountComponentIntoNode() upper half |~mountComponentIntoNode() (platform agnostic) |-ReactReconciler.mountComponent() // scr-----> 1) | |-ReactCompositeComponent[T].mountComponent() scr:> 2)3) . _ | _... lower half |-_mountImageIntoNode() (HTML DOM specific)Copy the code

This is what we covered in the first part of {post two}.

Except for some small differences in regard to argument values, the the top level wrapper related operations are exactly the same as what we discussed in previous posts. After those operations complete, we came to the first ramification that is specific to class component.

ReactCompositeComponent[T].performInitialMount()– create aReactCompositeComponent from ReactElement[1]

This step strips the wrapper and creates another ReactCompositeComponent instance to reflect App component.

The designated data structure:

The call stack in action:

. |~mountComponentIntoNode() | |-ReactReconciler.mountComponent() | |-ReactCompositeComponent[T].mountComponent() | /* we are here */ | |-ReactCompositeComponent[T].performInitialMount( | renderedElement, // scr: -------> undefined | hostParent, // scr: -------> null upper half hostContainerInfo, // scr: -------> | ReactDOMContainerInfo[ins] | transaction, // scr: -------> not of interest | context, // scr: -------> not of interest | ) |Copy the code

The process is very similar to the performInitialMount() in {post two}. The only difference here is that based on the type of ReactElement[1], _instantiateReactComponent creates a ReactCompositeComponent for the class component (App) instead of a ReactDOMComponent. To put it briefly:

1) it calls _renderValidatedComponent() which in turn calls TopLevelWrapper.render() to extract ReactElement[1]; 2) it instantiates a ReactCompositeComponent with _instantiateReactComponent (we name the object ReactCompositeComponent[ins]); and 3) it calls ReactCompositeComponent[ins].mountComponent (recursively) through ReactReconciler, and move on to the next step.

performInitialMount: function (
  renderedElement,
  hostParent,
  hostContainerInfo,
  transaction,
  context) 
{
  var inst = this._instance;
...
  if (inst.componentWillMount) {
... // scr: we did not define componentWillMount() in App
  }
// If not a stateless component, we now render
  if (renderedElement === undefined) {
    renderedElement = this._renderValidatedComponent(); // scr: > 1)
  }
  var nodeType = ReactNodeTypes.getType(renderedElement); // scr: -> the type is ReactNodeTypes.Composite this time
  this._renderedNodeType = nodeType;
  var child = this._instantiateReactComponent(renderedElement, nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
  );      // scr: ----------------------------------------------> 2)
  this._renderedComponent = child;
  var markup = ReactReconciler.mountComponent(child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID); // scr: ----------------------------------------------> 3)
...// scr: DEV code
  return markup;
},
ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.jsCopy the code

ReactCompositeComponent[1].mountComponent()– the initializeReactCompositeComponent[1]

The designated data structure:

The call stack in action:

. |~mountComponentIntoNode() | |-ReactReconciler.mountComponent() | |-ReactCompositeComponent[T].mountComponent() | |-ReactCompositeComponent[T].performInitialMount() upper half |-ReactReconciler.mountComponent() | /* we are here */ | |-ReactCompositeComponent[1].mountComponent(same) |Copy the code

Same as in ReactCompositeComponent[T].mountComponent() {post two}, the most important task of this step is to instantiate App with ReactCompositeComponent[ins]._currentElement (ReactElement[1]).

The line in the method that does the job is:

. var inst = this._constructComponent( doConstruct, publicProps, publicContext, updateQueue, ); . ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.jsCopy the code

in which the constructor of App gets called.

. constructor(props) { super(props); this.state = { desc: 'start', }; }... // copied from the beginning of this textCopy the code

Then (we name it) App[ins] is set to ReactCompositeComponent[ins]._instance and a back-link is also created through ReactInstanceMap.

Other operations includes: 1) App[ins].props reference ReactElement[1].props; and 2) ReactCompositeComponent[ins]._mountOrder is 2 due to the ++ operating on the global variable nextMountID.

It is important to note that App[ins].render() is another App method we define in the beginning. Unlike TopLevelWrapper[ins].render() that returns a concrete ReactElement instance, App[ins].render() relies on React.createElement() at the time when it is invoked. We will revisit this method soon.

Since this step is very similar to that initializes the ReactCompositeComponent[T] {post two}, we do not further examine the workhorse method (i.e., mountComponent()).

ReactCompositeComponent[ins].performInitialMount()– create aReactDOMComponent

. |~mountComponentIntoNode() | |-ReactReconciler.mountComponent() | |-ReactCompositeComponent[T].mountComponent() | |-ReactCompositeComponent[T].performInitialMount() upper half |-ReactReconciler.mountComponent() | /* we are here */ | |-ReactCompositeComponent[1].mountComponent() | |-this.performInitialMount() | |-this._renderValidatedComponent() | |-instantiateReactComponent() _|_ |-ReactDOMComponent[6].mountComponent() lower halfCopy the code

Before the a ReactDOMComponent (we know that this is the class that handle DOM operations) can be created, the ReactElements defined within App[ins] needs to be extracted. To do so, App[ins].render() is called by the following line (in _renderValidatedComponent()) {post two}

. renderedElement = this._renderValidatedComponent(); . performInitialMount@renderers/shared/stack/reconciler/ReactCompositeComponent.jsCopy the code

Then App[ins].render() triggers

The cascading calls of React.createElement()

To understand how the ReactElement tree is established, let’s first revisit the App. Render () implementation:

render() {
    return React.createElement(           // scr: -----------> 5)
      'div',
      { className: 'App' },
      React.createElement(                // scr: -----------> 3)
        'div',
        { className: 'App-header' },
        React.createElement(              // scr: -----------> 1)
          'img',
          { src: "main.jpg", className: 'App-logo', alt: 'logo' }
        ),
        React.createElement(              // scr: -----------> 2)
          'h1',
          null,
          ' "Welcom to React" '
        )
      ),
      React.createElement(                // scr: -----------> 4)
        'p',
        { className: 'App-intro' },
        this.state.desc
      )
    );
  }
// copied from the beginning of this textCopy the code

In this code snippet I also give the call order of createElement()s which follows a very simple principle: arguments should be resolved (with createElement()) from left to right before a function (of createElement()) gets called.

Then we can examine the creation of each ReactElement {post one}.

React. The createElement method (/ / SCR: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- > 1) "img", {SRC: "main. JPG", the className: 'App - logo, Alt: "Logo"}),Copy the code

creates ReactElement[2]:

; and

The React. The createElement method (/ / SCR: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- > 2) "h1", null, "Welcome to React")Copy the code

creates ReactElement[3]:

(Now the two arguments for 3) are resolved.)

; and

React.createElement(                // scr: -----------> 3)
  'div',
  ReactElement[2],
  ReactElement[3]
),Copy the code

creates ReactElement[4]:

; and

React.createElement(                // scr: -----------> 4)
  'p',
  { className: 'App-intro' },
  this.state.desc
)Copy the code

creates ReactElement[5]:

(Now the arguments for 5) are resolved.)

; and

return React.createElement(           // scr: -----------> 5)
  'div',
  { className: 'App' },
  ReactElement[4],
  ReactElement[5]
)Copy the code

creates ReactElement[6]:

Combined together we got the element tree referenced by renderedElement:

ReactCompositeComponent[ins]._instantiateReactComponent()– CreateReactDOMComponent[6]

The designated data structure:

Then the element tree is used to create the ReactDOMComponent[6] by the following line (within _instantiateReactComponent()) {post two}

var child = this._instantiateReactComponent( renderedElement, nodeType ! == ReactNodeTypes.EMPTY /* shouldHaveDebugID */, );Copy the code

Now ReactReconciler.mountComponent() calls the mountComponent() of the ReactDOMComponent[6] and the logic processes to the lower half.

To be continued…