Photo by Gerrie van der Walt on Unsplash
Interface updates are essentially data changes. React provides a very intuitive front-end framework by converging everything that moves to states. I also like reviewing React code because I tend to start with data structures so I can build a preliminary understanding of the entire logic before diving into the details. I’ve often wondered about the React implementation, and this article is the result.
I have always believed that a controllable project depends on an understanding of the underlying library implementation. Whether it is magic changes, contribute code, or daily upgrades can be more stable.
This article will get through one of React’s critical paths by rendering a simple component. (Composition components, interface updates, and other topics will be discussed in a future article)
Files used in this article:
Isomorphic/React. Js: ReactElement. The createElement method () of the entrance
Isomorphic/classic/element/ReactElement. Js: ReactElement. The createElement method () implementation
Renderers/dom/ReactDOM. Js: ReactDOM. The render () of the entrance
Renderers/dom/client/ReactMount. Js: ReactDom. The render () implementation
Renderers/Shared/stack/reconciler/instantiateReactComponent js: based on the element type create components (ReactComponents)
Renderers/Shared/stack/reconciler/ReactCompositeComponent js: top-level element ReactComponents packaging
The label used in the call stack
– Function call
= the alias
~ indirect call
Because React flattens the component, the location of the file is not easy to see from the import statement, so I use the @ tag to mark the corresponding file path in the code block.
From the JSX toReact.createElement()
JSX is called at compile time by Babel translating to react.createElement (). For example, create-react-app comes with app.js:
Import React, {Component} from 'React'; Import the logo from '. / logo. SVG '; The import '. / App. CSS '; Class App extends Component {render() {return (<div className= "App" > <header className= "app-header" > <img SRC ={logo} ClassName = "app-logo" Alt = "logo" /> <h1 className= "app-title" >Welcome to React</h1> </header> <p className= "app-intro" > To get started, edit <code>src/App.js</code> and save to reload. </p> </div> ); } } export default App;Copy the code
Would be translated as:
Import React, {Component} from 'React'; Import the logo from '. / logo. SVG '; The import '. / App. CSS '; Class App extends Component {render() {return react. createElement(' div ', {className: 'App'}, react. createElement(' header ', {className: 'app-header'}, react. createElement(' img ', {SRC: logo, className: 'app-logo', Alt: 'logo'), react. createElement(' h1 ', {className: 'app-title'}, 'Welcome to React'), React. CreateElement (' p ', {className: 'app-intro'}, 'To get started, edit', react. createElement(' code ', null, 'SRC/app.js'),' and save To reload. '); } } export default App;Copy the code
The ReactElement returned by this function is then rendered in the application layer’s “index.js” :
Reactdom.render (<App />, document.getelementByid (' root '));Copy the code
(This process should be well known)
The component tree above is a bit complicated to get started with, so it’s best to pry open the React implementation with a simpler 🌰.
... Render (<h1 style={{" color ":" blue "}}>hello world</h1>, document.getelementbyid (' root ')); ...Copy the code
After the translation:
... Reactdom.render (react.createElement (' h1 ', {style: {" color ": "Blue"}}, 'Hello world'), document.getelementById (' root ')); ...Copy the code
React.createElement()
– Create aReactElement
The first step didn’t really do much. Simply instantiate a ReactElement and initialize it with the parameters passed in. The goal structure of this step is:
The call stack for this step:
The React. The createElement method. | = ReactElement createElement method (type, the config, children) | - ReactElement (type,... , props)Copy the code
1. React. CreateElement method (type, the config, children) is only a ReactElement. The createElement method (an alias);
... var createElement = ReactElement.createElement; ... Var React = {... The createElement method: createElement method,... }; module.exports = React; React@isomorphic/React.jsCopy the code
2. ReactElement. CreateElement method (type, the config, children) do three things: 1) copy the config object to props. 2) copy the children to props. 3) copy the type.
... // 1) if (config ! = null) {... Extracting not interesting properties from config... // Remaining properties are added to a new props object for (propName in config) { if ( hasOwnProperty.call(config, propName) && ! RESERVED_PROPS.hasOwnProperty(propName) ) { props[propName] = config[propName]; } } } // 2) // Children can be more than one argument, // The newly allocated props object. Var childrenLength = arguments. ChildrenLength -- 2; if (childrenLength === 1) { props.children = children; // scr: one child is stored as object } else if (childrenLength > 1) { var childArray = Array(childrenLength); for (var i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; // scr: multiple children are stored as array } props.children = childArray; } // 3) // Resolve default props if (type && type.defaultProps) { var defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props, ); ... ReactElement.createElement@isomorphic/classic/element/ReactElement.jsCopy the code
3. Then ReactElement (type,… , props) passes type and props as is to the constructor of ReactElement and returns an instance of the new construct.
... var ReactElement = function(type, key, ref, self, source, owner, props) { // This tag allow us to uniquely identify this as a React Element ? Typeof: REACT_ELEMENT_TYPE, // built-in properties that belong on the element type: // SCR: --------------> 'h1' key: // scr: --------------> not of interest for now ref: // scr: --------------> not of interest for now props: { children: // SCR: --------------> 'Hello world'... Other props: / / SCR: -- -- -- -- -- -- -- -- -- -- -- -- -- -- > style: {" color ": "Blue"}}, // Record the component responsible for creating this element. _owner: // SCR: --------------> null}; ... ReactElement@isomorphic/classic/element/ReactElement.jsCopy the code
This new building ReactElement moments later in ReactMount. InstantiateReactComponent () function is used. Since the next step will also build a ReactElement, we will first name the object generated in this step ReactElement[1].
ReactDom.render()
– Start rendering
_renderSubtreeIntoContainer()
– toReactElement[1]
addTopLevelWrapper
The next goal is to wrap ReactElement[1] into another ReactElement (let’s call it [2]) and assign ReactElement. Type to TopLevelWrapper. The name TopLevelWrapper is quite revealing – the wrapper for the top element (passed into render()) :
The TopLevelWrapper definition here is important, so I put three asterisks *** here for you to search for when you come to this article.
... var TopLevelWrapper = function() { this.rootID = topLevelRootCounter++; }; TopLevelWrapper.prototype.isReactComponent = {}; TopLevelWrapper.prototype.render = function() { // scr: this function will be used to strip the wrapper later in the // rendering process return this.props.child; }; TopLevelWrapper.isReactTopLevelWrapper = true; ... TopLevelWrapper@renderers/dom/client/ReactMount.jsCopy the code
ReactElement. Type is passed in a type (TopLevelWrapper). This type will be instantiated later in the rendering process. The Render () function is used to extract the ReactElement[1] contained in this.props. Child.
The call stack for this step:
ReactDOM.render |=ReactMount.render(nextElement, container, callback) |=ReactMount._renderSubtreeIntoContainer( parentComponent, // scr: --------------> null nextElement, // scr: -- -- -- -- -- -- -- -- -- -- -- -- -- -- > ReactElement [1], the container / / SCR: -- -- -- -- -- -- -- -- -- -- -- -- -- - > document. The getElementById (" root ") callback '/ / SCR: --------------> undefined )Copy the code
For the first time rendering, ReactMount. _renderSubtreeIntoContainer () is much simpler than it is, because most of the branches are skipped. The only valid code in this stage function is:
... var nextWrappedElement = React.createElement(TopLevelWrapper, { child: nextElement, }); ... _renderSubtreeIntoContainer@renderers/dom/client/ReactMount.jsCopy the code
We just looked at react.createElement (), so the build process for this step should be easy to understand. I won’t go into it here.
InstantiateReactComponent () – use ReactElement create a ReactCompositeComponent [2]
This step creates an initial ReactCompositeComponent for the top-level component:
The call stack:
ReactDOM.render |=ReactMount.render(nextElement, container, callback) |=ReactMount._renderSubtreeIntoContainer() |-ReactMount._renderNewRootComponent( nextWrappedElement, // scr: ------> ReactElement[2] container, // SCR: ------> document.getElementById(' root ') shouldReuseMarkup, // SCR: null from ReactDom.render() nextContext, // scr: emptyObject from ReactDom.render() ) |-instantiateReactComponent( node, // scr: ------> ReactElement[2] shouldHaveDebugID /* false */ ) |-ReactCompositeComponentWrapper( element // scr: ------> ReactElement[2] ); |=ReactCompositeComponent.construct(element)Copy the code
InstantiateReactComponent is the only one more complex functions. In this context, the function creates a ReactCompositeComponent based on the value of the field ReactElement[2].type (TopLevelWrapper)
function instantiateReactComponent(node, shouldHaveDebugID) { var instance; ... } else if (typeof node === 'object') {var Element = node; var type = element.type; ... // Special Case String values if (typeof element.type === 'string') {... } else if (isInternalComponentType(element.type)) {... } else { instance = new ReactCompositeComponentWrapper(element); }} else if (typeof node = = = "string" | | typeof node = = = 'number') {... } else {... }... return instance; } instantiateReactComponent@renderers/shared/stack/reconciler/instantiateReactComponent.jsCopy the code
Here is it is worth noting that the new ReactCompositeComponentWrapper ()
... // To avoid a cyclic dependency, we create the final class in this module var ReactCompositeComponentWrapper = function(element) { this.construct(element); }; ... ... Object.assign( ReactCompositeComponentWrapper.prototype, ReactCompositeComponent, { _instantiateReactComponent: instantiateReactComponent, }, ); ... ReactCompositeComponentWrapper@renderers/shared/stack/reconciler/instantiateReactComponent.jsCopy the code
The ReactCompositeComponent constructor is actually called directly:
construct: function(element /* scr: ------> ReactElement[2] */) {
this._currentElement = element;
this._rootNodeID = 0;
this._compositeType = null;
this._instance = null;
this._hostParent = null;
this._hostContainerInfo = null;
// See ReactUpdateQueue
this._updateBatchNumber = null;
this._pendingElement = null;
this._pendingStateQueue = null;
this._pendingReplaceState = false;
this._pendingForceUpdate = false;
this._renderedNodeType = null;
this._renderedComponent = null;
this._context = null;
this._mountOrder = 0;
this._topLevelWrapper = null;
// See ReactUpdates and ReactUpdateQueue.
this._pendingCallbacks = null;
// ComponentWillUnmount shall only be called once
this._calledComponentWillUnmount = false;
},
ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js
Copy the code
In subsequent steps ReactCompositeComponent will be instantiateReactComponent () to create, so we take this step generated objects named ReactCompositeComponent [T] (T represents the top).
After ReactCompositeComponent [T] created, the next step will React to invoke batchedMountComponentIntoNode, to initialize the component object, then render it and inserted into the DOM tree. This process will be discussed in the next article.
That’s where I’ll start today. If you think this post is good, please like it or follow this column.
Thanks for reading! 👋
Originally published at holmeshe.me.