Preface:

After working with React for so long, it’s time to have a look at the source code. I was going to go straight to the source code for React16, but after adding React Fiber, it looks like a pain. No way can only chew the old first, do not learn to keep up!

React uses a lot of transactions

1. The transaction transaction

Let’s take a look at the code implementation of the transaction:

var Transaction = {
    reinitializeTransaction: function() {
        this.transactionWrappers = this.getTransactionWrappers();
        if (this.wrapperInitData) {
            this.wrapperInitData.length = 0;
        } else {
            this.wrapperInitData = [];
        }
        this._isInTransaction = false;
    },
    _isInTransaction: false.getTransactionWrappers: null.perform: function(method, scope, a, b, c, d, e, f) {
        var ret;
        try {
            this._isInTransaction = true;
            this.initializeAll(0);
            ret = method.call(scope, a, b, c, d, e, f);
        } finally {
            this.closeAll(0);
            this._isInTransaction = false;
        }
        return ret;
    },
    initializeAll: function() {
        var transactionWrappers = this.transactionWrappers;
        for (var i = startIndex; i < transactionWrappers.length; i++) {
            var wrapper = transactionWrappers[i];
            this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null; }},closeAll: function(startIndex) {
        var transactionWrappers = this.transactionWrappers;
        for (var i = startIndex; i < transactionWrappers.length; i++) {
            var wrapper = transactionWrappers[i];
            var initData = this.wrapperInitData[i];
            wrapper.close.call(this, initData);
        }
        this.wrapperInitData.length = 0; }};Copy the code

Here’s an example:

function update(){
    console.log('Updated')}; Transaction.perform(update); InitializeAll is executed before the update method is executed, and the initialize method is looping // output'Updated'// Execute closeAll and loop through the close method in transactionWrappersCopy the code

This transaction simply abstracts getTransactionWrappers to null, which requires subclasses to implement:

That is, do something before and after the method is executed!

Below we see ReactReconcileTransaction (coordinated transaction implementation)

var SELECTION_RESTORATION = {
    initialize: ReactInputSelection.getSelectionInformation,
    close: ReactInputSelection.restoreSelection
};
var EVENT_SUPPRESSION = {
    initialize: function() {
        var currentlyEnabled = ReactBrowserEventEmitter.isEnabled();
        ReactBrowserEventEmitter.setEnabled(false);
        return currentlyEnabled;
    },
    close: function(previouslyEnabled) { ReactBrowserEventEmitter.setEnabled(previouslyEnabled); }}; var ON_DOM_READY_QUEUEING = { initialize:function() {
        this.reactMountReady.reset();
    },
    close: function() { this.reactMountReady.notifyAll(); }}; var TRANSACTION_WRAPPERS = [SELECTION_RESTORATION, EVENT_SUPPRESSION, ON_DOM_READY_QUEUEING];function ReactReconcileTransaction(useCreateElement) {
    this.reinitializeTransaction();
}
var Mixin = {
    getTransactionWrappers: function() {
        return TRANSACTION_WRAPPERS;
    },
    getUpdateQueue: function() {
        returnReactUpdateQueue; }}; _assign(ReactReconcileTransaction.prototype, Transaction, Mixin);Copy the code


ReactReconcileTransaction inherited Transaction, rewrite the getTransactionWrappers getTransactionWrappers method:function () {    return TRANSACTION_WRAPPERS;  },Copy the code
  • SELECTION_RESTORATION: When the DOM is updated, the focus element and selection are collected. When the DOM is updated, the focus and selection are restored
  • EVENT_SUPPRESSION: Turns off event response before update and turns on event response after update
  • ON_DOM_READY_QUEUEING: Executes something like componentDidMount after the DOM update is complete

Official source code:



Simply put, a Transaction is a Method that needs to be executed wrapped in a Wrapper and executed using the Perform method provided by Transaction. Before performing, initialize methods in all wrappers are executed; Perform all of the close methods after perform. A set of initialize and close methods is called a wrapper, and as you can see from the example above, Transaction supports multiple wrapper stacks.

Let’s get down to business:

2. Use an example to illustrate the process

import React from 'react';
import ReactDOM from 'react-dom';
class HelloWorld extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            message: "hello, world",
            className: 'react-wrap'}}componentWillMount() {
        debugger console.log("component will mount");
    }
    componentWillUpdate() {
        debugger console.log("component will update");
    }
    componentDidUpdate() {
        debugger console.log("component did update");
    }
    componentDidMount() {
        debugger console.log("componentDidMount");
    }
    handleMessage() {
        debugger this.setState({
            message: 'ha ha',
            className: 'list-wrap'
        });
    }
    render() {
        return < span className = {
            this.state.className
        }
        onClick = {
            this.handleMessage.bind(this)
        } > {
            this.state.message
        } < /span>  }}ReactDOM.render( <HelloWorld/ > ,
        document.getElementById('screen-check'Reactdom.render (react.createElement (HelloWorld, null), document.getelementById ('screen-check'));


Copy the code

after the conversion is react. createElement(HelloWorld, null);

ReactElement.createElement = function(type, config, children) {
    var propName;
    var props = {};
    var key = null;
    var ref = null;
    var self = null;
    var source = null;
    if(config ! = null) {if (hasValidRef(config)) {
            ref = config.ref;
        }
        if (hasValidKey(config)) {
            key = ' ' + config.key;
        }
        self = config.__self === undefined ? null: config.__self;
        source = config.__source === undefined ? null: config.__source;
        for (propName in config) {
            if(hasOwnProperty.call(config, propName) && ! RESERVED_PROPS.hasOwnProperty(propName)) { props[propName] = config[propName]; } } } var childrenLength = arguments.length - 2;if (childrenLength === 1) {
        props.children = children;
    } else if (childrenLength > 1) {
        var childArray = Array(childrenLength);
        for (var i = 0; i < childrenLength; i++) {
            childArray[i] = arguments[i + 2];
        }
        props.children = childArray;
    }
    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);
};Copy the code

CreateElement calls ReactElement after the parameters are collated

Returns:



Get Element, then call Render



Among them: nextElement: < the HelloWorld / > container: the document. The getElementById (‘ screen – check ‘) then call _renderSubtreeIntoContainer,

NextElement package nextWrappedElement to keep the root component unified and easy to handle

_renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) {
    var nextWrappedElement = React.createElement(TopLevelWrapper, {
        child: nextElement
    });
    var nextContext;
    if (parentComponent) {
        var parentInst = ReactInstanceMap.get(parentComponent);
        nextContext = parentInst._processChildContext(parentInst._context);
    } else {
        nextContext = emptyObject;
    }
    var prevComponent = getTopLevelWrapperInContainer(container);
    if (prevComponent) {
        var prevWrappedElement = prevComponent._currentElement;
        var prevElement = prevWrappedElement.props.child;
        if (shouldUpdateReactComponent(prevElement, nextElement)) {
            var publicInst = prevComponent._renderedComponent.getPublicInstance();
            var updatedCallback = callback &&
            function() {
                callback.call(publicInst);
            };
            ReactMount._updateRootComponent(prevComponent, nextWrappedElement, nextContext, container, updatedCallback);
            return publicInst;
        } else{ ReactMount.unmountComponentAtNode(container); } } var reactRootElement = getReactRootElementInContainer(container); var containerHasReactMarkup = reactRootElement && !! internalGetID(reactRootElement); var containerHasNonRootReactChild = hasNonRootReactChild(container); var shouldReuseMarkup = containerHasReactMarkup && ! prevComponent && ! containerHasNonRootReactChild; var component = ReactMount._renderNewRootComponent(nextWrappedElement, container, shouldReuseMarkup, nextContext)._renderedComponent.getPublicInstance();if (callback) {
        callback.call(component);
    }
    return component;
},
Copy the code

First mount,prevComponent does not exist, call _renderNewRootComponent directly

ReactMount._renderNewRootComponent(nextWrappedElement, container, shouldReuseMarkup, nextContext)Copy the code
NextWrappedElement structure:

_renderNewRootComponent: function(nextElement, container, shouldReuseMarkup, context) {
    var componentInstance = instantiateReactComponent(nextElement, false);
    ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);

    var wrapperID = componentInstance._instance.rootID;
    instancesByReactRootID[wrapperID] = componentInstance;

    return componentInstance;
},
Copy the code

InstantiateReactComponent () to instantiate the component according to the different type field ReactElement, create different types of component objects. The source code is as follows

    • // Initialize the component object. Node is a ReactElement object that represents node elements in Reactfunction instantiateReactComponent(node) {
        var instance;
      
        var isEmpty = node === null || node === false;
        ifIsEmpty () {/ / empty object instance = ReactEmptyComponent. Create (instantiateReactComponent); }else if (typeof node === 'object'Var element = node; var element = node; // According to ReactElementtypeField to distinguish betweenif (typeof element.type === 'string'{/ /typeA string represents a DOM native object, such as div, SPAN, etc. See Babel translation of the code above instance. = ReactNativeComponent createInternalComponent (element); }else if(isInternalComponentType(element.type)) {// Save for later use. Instance = new element.type(element); }else{/ / React custom component instance = new ReactCompositeComponentWrapper (element); }}else if (typeof node === 'string' || typeof node === 'number'<span>123</span> 123 // it is not a ReactElement per se, but it follows the same process for consistency. Called ReactDOMTextComponent instance = ReactNativeComponent. CreateInstanceForText (node); }elseInstance. _mountIndex = 0; instance._mountIndex = 0; instance._mountImage = null;return instance;
      }Copy the code

      • ReactEmptyComponent. The create (), create an empty object ReactDOMEmptyComponent
      • ReactNativeComponent. CreateInternalComponent (), create DOM native objects
      • ReactDOMComponent new ReactCompositeComponentWrapper (), create the React ReactCompositeComponent custom object
      • ReactNativeComponent. CreateInstanceForText (), create a text object ReactDOMTextComponent
node The actual parameter The results of
null/false empty createReactEmptyComponentcomponent
object && type === string Virtual DOM createReactDOMComponentcomponent
object && type ! == string The React components createReactCompositeComponentcomponent
string string createReactTextComponentcomponent
number digital createReactTextComponentcomponent



var ReactCompositeComponent = {
    construct: function(element) {
        this._currentElement = element;
        this._rootNodeID = 0;
        this._compositeType = null;
        this._instance = null;
        this._hostParent = null;
        this._hostContainerInfo = null;
        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;
        this._pendingCallbacks = null;
        this._calledComponentWillUnmount = false;
    },
    mountComponent: function(transaction, hostParent, hostContainerInfo, context) {}
    
    ...
}Copy the code
ComponentInstance structure:

BatchedUpdates Batch update

ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);Copy the code

function batchedUpdates(callback, a, b, c, d, e) {
  return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}Copy the code

Check out the batchingStrategy code:

var RESET_BATCHED_UPDATES = {
    initialize: emptyFunction,
    close: function() {
        ReactDefaultBatchingStrategy.isBatchingUpdates = false; }}; var FLUSH_BATCHED_UPDATES = { initialize: emptyFunction, close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates) }; var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];function ReactDefaultBatchingStrategyTransaction() {
    this.reinitializeTransaction();
}
_assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
    getTransactionWrappers: function() {
        returnTRANSACTION_WRAPPERS; }}); var transaction = new ReactDefaultBatchingStrategyTransaction(); var ReactDefaultBatchingStrategy = { isBatchingUpdates:false,
    batchedUpdates: function(callback, a, b, c, d, e) {
        var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

        ReactDefaultBatchingStrategy.isBatchingUpdates = true;
        if (alreadyBatchingUpdates) {
            return callback(a, b, c, d, e);
        } else {
            returntransaction.perform(callback, null, a, b, c, d, e); }}}; module.exports = ReactDefaultBatchingStrategy;Copy the code

ReactDefaultBatchingStrategy. BatchedUpdates content using the mentioned at the beginning of the transaction

Before performing batchedMountComponentIntoNode RESET_BATCHED_UPDATES, FLUSH_BATCHED_UPDATES initialize method, there is an empty function

Next comes the transaction invocation

transaction.perform(callback, null, a, b, c, d, e);Copy the code

This is a transaction nesting:

The execution process is shown as follows:



According to the transaction detail diagram above

  1. In the ReactDefaultBatchingStrategy
  • FLUSH_BATCHED_UPDATES. Initialize is empty
  • RESET_BATCHED_UPDATES. Initialize is empty

ReactReconcileTransaction affairs

  • Selection_restoration. initialize gets pre-update focus elements for post-update restoration (such as input focus)
  • EVENT_SUPPRESSION. Initialize, disables the event response
  • ON_DOM_READY_QUEUEING initializes the onDOMReady list

Here is a summary:

Next, through two transaction nesting, execute

functionmountComponentIntoNode(wrapperInstance, container, transaction, shouldReuseMarkup, Context) {// Call the mountComponent method in the corresponding ReactCompositeComponent to render the component. // mountComponent returns the HTML parsed by the React component. Different ReactComponent mountComponent strategy of var markup. = ReactReconciler mountComponent (wrapperInstance, transaction, null, ReactDOMContainerInfo(wrapperInstance, container), context, 0 /* parentDebugID */ ); wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance; // Insert the parsed HTML into the DOM reactmount._mountimageintonode (markup, container, wrapperInstance, shouldReuseMarkup); }Copy the code

Enter mountComponentIntoNode read markup call ReactReconciler mountComponent

mountComponent: function (internalInstance, transaction, hostParent, hostContainerInfo, context, parentDebugID){
    var markup = internalInstance.mountComponent(transaction, hostParent, hostContainerInfo, context, parentDebugID);
    return markup;
  }Copy the code

ReactReconciler. MountComponent is used in the corresponding instance of the module to invoke their mountComponent method

The instance here, internalInstance, corresponds to an instance of the ReactCompositeComponent type

var ReactCompositeComponent = {
    construct: function(element) { //... Omit}, mountComponent:function(transaction, hostParent, hostContainerInfo, context) {
        var _this = this;
        this._context = context;
        this._mountOrder = nextMountID++;
        this._hostParent = hostParent;
        this._hostContainerInfo = hostContainerInfo;

        var publicProps = this._currentElement.props;
        var publicContext = this._processContext(context);

        var Component = this._currentElement.type;

        var updateQueue = transaction.getUpdateQueue();

        // Initialize the public class
        var doConstruct = shouldConstruct(Component);
        var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);
        var renderedElement;

        if (!doConstruct && (inst == null || inst.render == null)) {
            renderedElement = inst;
            warnIfInvalidElement(Component, renderedElement); ! (inst === null || inst === false|| React.isValidElement(inst)) ? process.env.NODE_ENV ! = ='production' ? invariant(false.'%s(...) : A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.', Component.displayName || Component.name || 'Component') : _prodInvariant('105', Component.displayName || Component.name || 'Component') : void 0;
            inst = new StatelessComponent(Component);
            this._compositeType = CompositeTypes.StatelessFunctional;
        } else {
            if (isPureComponent(Component)) {
                this._compositeType = CompositeTypes.PureClass;
            } else {
                this._compositeType = CompositeTypes.ImpureClass;
            }
        }
        inst.props = publicProps;
        inst.context = publicContext;
        inst.refs = emptyObject;
        inst.updater = updateQueue;
        this._instance = inst;
        ReactInstanceMap.set(inst, this);
        var initialState = inst.state;
        if(initialState === undefined) { inst.state = initialState = null; }! (typeof initialState ==='object'&&! Array.isArray(initialState)) ? process.env.NODE_ENV ! = ='production' ? invariant(false.'%s.state: must be set to an object or null', this.getName() || 'ReactCompositeComponent') : _prodInvariant('106', this.getName() || 'ReactCompositeComponent') : void 0;

        this._pendingStateQueue = null;
        this._pendingReplaceState = false;
        this._pendingForceUpdate = false;

        var markup;
        markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);

        if (inst.componentDidMount) {
            transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
        }
        returnmarkup; }}Copy the code

This._processContext(context);

_processContext: function(context) {
    var maskedContext = this._maskContext(context);
    return maskedContext;
},Copy the code

When using ContextAPI, define in a custom component:

As you can see from the code, if the component does not define contextTypes, the Context is returned as {}, with no value




Next, create the instance

var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);

_constructComponent: function (doConstruct, publicProps, publicContext, updateQueue) {
      return this._constructComponentWithoutOwner(doConstruct, publicProps, publicContext, updateQueue);
  },Copy the code

 _constructComponentWithoutOwner: function (doConstruct, publicProps, publicContext, updateQueue) {
    var Component = this._currentElement.type;
        return new Component(publicProps, publicContext, updateQueue);
  }Copy the code

The instance is created by the previously wrapped root component



Here’s how to determine a custom component:



function isPureComponent(Component) {
  return!!!!! (Component.prototype && Component.prototype.isPureReactComponent); }Copy the code

We all know that custom components come in two forms:

class Greeting extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
或者
class Greeting extends React.PureComponent {
  render() {
    return<h1>Hello, {this.props.name}</h1>; }}Copy the code

React.PureComponent is very similar to react.component.react.component.react.pureComponent. The difference is that shouldComponentUpdate() is not implemented in react.pureComponent, which is implemented in a shallow way to compare prop and state.

ShouldComponentUpdate () in react.pureComponent is only a superficial comparison of objects

ShouldComponentUpdate () shouldComponentUpdate() should make a shallow comparison

inst.props = publicProps; inst.context = publicContext; inst.refs = emptyObject; inst.updater = updateQueue; / / forsetWhen the Sate and foreUpdate methods are calledCopy the code


The ReactCompositeComponent instance imports the TopLevelWrapper root component instance through the _instance property

The TopLevelWrapper component instance references the ReactCompositeComponent instance via the _reactInternalInstance property

The mount is then initialized

performInitialMount: function(renderedElement, hostParent, hostContainerInfo, transaction, context) {
    var inst = this._instance;
    var debugID = 0;
    if (inst.componentWillMount) {
        inst.componentWillMount();
        // When mounting, calls to `setState` by `componentWillMount` will set
        // `this._pendingStateQueue` without triggering a re-render.
        if(this._pendingStateQueue) { inst.state = this._processPendingState(inst.props, inst.context); }}if(renderedElement === undefined) { renderedElement = this._renderValidatedComponent(); } var nodeType = ReactNodeTypes.getType(renderedElement); this._renderedNodeType = nodeType; var child = this._instantiateReactComponent(renderedElement, nodeType ! == ReactNodeTypes.EMPTY /* shouldHaveDebugID */ ); this._renderedComponent = child; var markup = ReactReconciler.mountComponent(child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID);return markup;
}Copy the code

Here comes the first life cycle componentWillMount

The wrapper root component is skipped without defining componentWillMount.

Then call the Render method



_renderValidatedComponentWithoutOwnerOrContext: function() {
    var inst = this._instance;
    var renderedElement;
    renderedElement = inst.render();
    return renderedElement;
}Copy the code



renderedElement=this._renderValidatedComponent();

This returns the <HelloWorld/> object we defined to enter our own world



Then there’s the recursive call

InstantiateReactComponent () to instantiate the component according to the different type field ReactElement, create different types of component objects



Create an instance of ReactCompositeComponent based on renderedElement’s Type Class HelloWorld



Then call the mountComponent method of the ReactCompositeComponent instance again



This time create the custom component HelloWorld


The lifecycle is defined when the ReactCompositeComponent instance corresponding to HelloWorld is initialized for mount



Start calling the first life cycle




And then I call Render

// If not a stateless component, we now render
    if (renderedElement === undefined) {
      renderedElement = this._renderValidatedComponent();
    }Copy the code

renderedElement = this._renderValidatedComponent();



The structure of the renderedElement is

This is followed by another recursive call:



The difference with this recursion is that the type of the renderedElement is String

According to the instantiateReactComponent () to instantiate the component, this time ReactDOMComponent component instance



The mountComponent of the ReactDOMComponent is also different from the ReactCompositeComponent instance

This involves creating the DOM, adding events, and being compatible with different browsers, and is the most complex of several components

ReactDOMComponent = {
    mountComponent: function(transaction, hostParent, hostContainerInfo, context) {
        this._rootNodeID = globalIdCounter++;
        this._domID = hostContainerInfo._idCounter++;
        this._hostParent = hostParent;
        this._hostContainerInfo = hostContainerInfo;

        var props = this._currentElement.props;

        switch (this._tag) {
        case 'audio':
        case 'form':
        case 'iframe':
        case 'img':
        case 'link':
        case 'object':
        case 'source':
        case 'video':
            this._wrapperState = {
                listeners: null
            };
            transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
            break;
        case 'input':
            ReactDOMInput.mountWrapper(this, props, hostParent);
            props = ReactDOMInput.getHostProps(this, props);
            transaction.getReactMountReady().enqueue(trackInputValue, this);
            transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
            break;
        case 'option':
            ReactDOMOption.mountWrapper(this, props, hostParent);
            props = ReactDOMOption.getHostProps(this, props);
            break;
        case 'select':
            ReactDOMSelect.mountWrapper(this, props, hostParent);
            props = ReactDOMSelect.getHostProps(this, props);
            transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
            break;
        case 'textarea':
            ReactDOMTextarea.mountWrapper(this, props, hostParent);
            props = ReactDOMTextarea.getHostProps(this, props);
            transaction.getReactMountReady().enqueue(trackInputValue, this);
            transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
            break;
        }
        var namespaceURI;
        var parentTag;
        if(hostParent ! = null) { namespaceURI = hostParent._namespaceURI; parentTag = hostParent._tag; }else if (hostContainerInfo._tag) {
            namespaceURI = hostContainerInfo._namespaceURI;
            parentTag = hostContainerInfo._tag;
        }
        if (namespaceURI == null || namespaceURI === DOMNamespaces.svg && parentTag === 'foreignobject') {
            namespaceURI = DOMNamespaces.html;
        }
        if (namespaceURI === DOMNamespaces.html) {
            if (this._tag === 'svg') {
                namespaceURI = DOMNamespaces.svg;
            } else if (this._tag === 'math') {
                namespaceURI = DOMNamespaces.mathml;
            }
        }
        this._namespaceURI = namespaceURI;

        if(process.env.NODE_ENV ! = ='production') {
            var parentInfo;
            if(hostParent ! = null) { parentInfo = hostParent._ancestorInfo; }else if (hostContainerInfo._tag) {
                parentInfo = hostContainerInfo._ancestorInfo;
            }
            if (parentInfo) {
                validateDOMNesting(this._tag, null, this, parentInfo);
            }
            this._ancestorInfo = validateDOMNesting.updatedAncestorInfo(parentInfo, this._tag, this);
        }

        var mountImage;
        if (transaction.useCreateElement) {
            var ownerDocument = hostContainerInfo._ownerDocument;
            var el;
            if (namespaceURI === DOMNamespaces.html) {
                if (this._tag === 'script') {
                    // Create the script via .innerHTML so its "parser-inserted" flag is
                    // set to true and it does not execute
                    var div = ownerDocument.createElement('div');
                    var type = this._currentElement.type;
                    div.innerHTML = '<' + type + '> < / + type + '>';
                    el = div.removeChild(div.firstChild);
                } else if (props.is) {
                    el = ownerDocument.createElement(this._currentElement.type, props.is);
                } else{ el = ownerDocument.createElement(this._currentElement.type); }}else {
                el = ownerDocument.createElementNS(namespaceURI, this._currentElement.type);
            }
            ReactDOMComponentTree.precacheNode(this, el);
            this._flags |= Flags.hasCachedChildNodes;
            if(! this._hostParent) { DOMPropertyOperations.setAttributeForRoot(el); } this._updateDOMProperties(null, props, transaction); var lazyTree = DOMLazyTree(el); this._createInitialChildren(transaction, props, context, lazyTree); mountImage = lazyTree; }else {
            var tagOpen = this._createOpenTagMarkupAndPutListeners(transaction, props);
            var tagContent = this._createContentMarkup(transaction, props, context);
            if(! tagContent && omittedCloseTags[this._tag]) { mountImage = tagOpen +'/ >';
            } else {
                mountImage = tagOpen + '>' + tagContent + '< /' + this._currentElement.type + '>';
            }
        }

        switch (this._tag) {
        case 'input':
            transaction.getReactMountReady().enqueue(inputPostMount, this);
            if (props.autoFocus) {
                transaction.getReactMountReady().enqueue(AutoFocusUtils.focusDOMComponent, this);
            }
            break;
        case 'textarea':
            transaction.getReactMountReady().enqueue(textareaPostMount, this);
            if (props.autoFocus) {
                transaction.getReactMountReady().enqueue(AutoFocusUtils.focusDOMComponent, this);
            }
            break;
        case 'select':
            if (props.autoFocus) {
                transaction.getReactMountReady().enqueue(AutoFocusUtils.focusDOMComponent, this);
            }
            break;
        case 'button':
            if (props.autoFocus) {
                transaction.getReactMountReady().enqueue(AutoFocusUtils.focusDOMComponent, this);
            }
            break;
        case 'option':
            transaction.getReactMountReady().enqueue(optionPostMount, this);
            break;
        }

        returnmountImage; }}Copy the code

The mountComponent of the ReactDOMComponent does that

Here we create the SPAN tag:



The SPAN node is then referenced in the ReactDOMComponent instance attribute _hostNode

Add “__reactInternalInstance$r5bs9lz1m3” to dom node span to reference the instance of ReactDOMComponent. InternalInstanceKey has the value “__reactInternalInstanc e$r5bs9lz1m3”



Next, add attributes to the DOM node

this._updateDOMProperties(null, props, transaction);

  • The first traversal deals with the className attribute



Find the property name for ‘className’ in domproperty. properties and set the classs property on the DOM node



  • The next thing to do is “onClick”, which starts registering event listeners:

function enqueuePutListener(inst, registrationName, listener, transaction) {
    var containerInfo = inst._hostContainerInfo;
    var isDocumentFragment = containerInfo._node && containerInfo._node.nodeType === DOC_FRAGMENT_TYPE;
    var doc = isDocumentFragment ? containerInfo._node: containerInfo._ownerDocument;
    listenTo(registrationName, doc);
    transaction.getReactMountReady().enqueue(putListener, {
        inst: inst,
        registrationName: registrationName,
        listener: listener
    });
}Copy the code

In listenTo (registrationName, doc); Is called

ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(dependency, topEventMapping[dependency], mountAt);Copy the code



The trapBubbledEvent method adds the click event to the Document method as a proxy

The registration event callback function here is very important,

Is behind click call processing events, the starting point of trigger setState, click events to trigger ReactEventListener. DispatchEvent

Intersperse transaction in the middle to improve the update efficiency of setState

trapBubbledEvent: function (topLevelType, handlerBaseName, element) {
   return EventListener.listen(element, handlerBaseName, ReactEventListener.dispatchEvent.bind(null, topLevelType));
  },Copy the code




listenTo(registrationName, doc); after

transaction.getReactMountReady().enqueue(putListener, {
    inst: inst,
    registrationName: registrationName,
    listener: listener
  });Copy the code

In the instance ReactReconcileTransaction corresponding affairs

Transaction. ReactMountReady array collection, etc. After the update, triggering the corresponding close method is invoked



Now we’re going to do ‘children’, which is not a DOM property

After updating the properties

this._updateDOMProperties(null, props, transaction); Var lazyTree = DOMLazyTree(el); this._createInitialChildren(transaction, props, context, lazyTree); mountImage = lazyTree;Copy the code



Process children in _createInitialChildrenCopy the code






Start mounting children, loop through the Children array



Back to

According to the instantiateReactComponent () to instantiate the component, this time ReactDOMTextComponent component instance

var ReactDOMTextComponent = function(text) {
    // TODO: This is really a ReactText (ReactNode), not a ReactElement
    this._currentElement = text;
    this._stringText = ' ' + text;
    // ReactDOMComponentTree uses these:
    this._hostNode = null;
    this._hostParent = null;

    // Properties
    this._domID = 0;
    this._mountIndex = 0;
    this._closingComment = null;
    this._commentNodes = null;
};Copy the code

mountComponent: function(transaction, hostParent, hostContainerInfo, context) {
    var domID = hostContainerInfo._idCounter++;
    var openingValue = ' react-text: ' + domID + ' ';
    var closingValue = ' /react-text ';
    this._domID = domID;
    this._hostParent = hostParent;
    if (transaction.useCreateElement) {
        var ownerDocument = hostContainerInfo._ownerDocument;
        var openingComment = ownerDocument.createComment(openingValue);
        var closingComment = ownerDocument.createComment(closingValue);
        var lazyTree = DOMLazyTree(ownerDocument.createDocumentFragment());
        DOMLazyTree.queueChild(lazyTree, DOMLazyTree(openingComment));
        if (this._stringText) {
            DOMLazyTree.queueChild(lazyTree, DOMLazyTree(ownerDocument.createTextNode(this._stringText)));
        }
        DOMLazyTree.queueChild(lazyTree, DOMLazyTree(closingComment));
        ReactDOMComponentTree.precacheNode(this, openingComment);
        this._closingComment = closingComment;
        return lazyTree;
    } else {
        var escapedText = escapeTextContentForBrowser(this._stringText);

        if (transaction.renderToStaticMarkup) {
            // Normally we'd wrap this between comment nodes for the reasons stated // above, but since this is a situation where React won't take over
            // (static pages), we can simply return the text as it is.
            return escapedText;
        }

        return '<! -- ' + openingValue + '-->' + escapedText + '<! -- ' + closingValue + '-->'; }}Copy the code

In mountChildren cycle children array, instantiateReactComponent instantiate the component ()

children



mountChildren: function (nestedChildren, transaction, context) {
      var children = this._reconcilerInstantiateChildren(nestedChildren, transaction, context);
      this._renderedChildren = children;

      var mountImages = [];
      var index = 0;
      for (var name in children) {
        if(children.hasOwnProperty(name)) { var child = children[name]; var selfDebugID = 0; var mountImage = ReactReconciler.mountComponent(child, transaction, this, this._hostContainerInfo, context, selfDebugID); child._mountIndex = index++; mountImages.push(mountImage); }}return mountImages;
    }Copy the code

It then loops through the Children object calling the ReactDOMTextComponent of the corresponding instancemountComponent

The mountComponent of ReactDOMTextComponent is simpler. Create the Document. createTextNode text node and insert the DocumentFragment


After processing, insert the DocumentFragment in a loop

The span of the lazyTree DOM node above





So the recursion completes the return

The HelloWorld ReactCompositeComponentWrapper corresponding instance

At this point, although the Dom node has been created, the page node has not been inserted

ComponentDidMount we know that componentDidMount is executed after the page is inserted, so the implementation here is to collect componentDidMount into the reactMountReady transaction and wait until the update is mounted to the page

There are now two stored here, one collected earlier when you added onClick


Then back to the corresponding TopLevelWrapper ReactCompositeComponentWrapper



The root wrapper component does not have componentDidMount, returns the markup directly, and now returns mountComponentIntoNode, which is the method that the two transactions will execute,

It is then time to insert the DOM node into the document and perform the transaction’s close



After inserting the Container page node, the mountComponentIntoNode method is called





According to the above transaction figure can know in detail, first ReactReconcileTransaction transaction

  • Selection_restoration.close Gets focus elements (such as input focus) before resuming
  • EVENT_SUPPRESSION. Close Enables event response
  • ON_DOM_READY_QUEUEING, which calls reactMountReady of the transaction collected during the update process
  • (added by componentDidMount and event listener above)
ComponentDidMount is a transaction that guarantees execution after the DOM is mounted


Perform first: putListener



Next :componentDidMount




  1. And then back to ReactDefaultBatchingStrategy
  • Flush_batched_updates. close // perform any updates queued by the mount-ready handler (i.e. SetState update called in componentDidUpdate)
  • RESET_BATCHED_UPDATES. Close to reset isBatchingUpdates. IsBatchingUpdates = false flag updates to an end

To sum up:

Reactdom.render () is the entry method to render the React component and insert it into the DOM

  1. Reace.createelement () creates the ReactElement object. Its important member variables are type,key,ref, and props. This is done by calling getInitialState(), which initializes the state and only calls it at mount time. It will not be called later on update.
  2. InstantiateReactComponent (), according to the type of ReactElement create ReactDOMComponent respectively, ReactCompositeComponent, Call the React lifecycle method to parse the component and get its HTML for an object such as mountComponent() such as ReactDOMTextComponent.
  3. _mountImageIntoNode(), inserts the HTML into the DOM parent by setting the innerHTML property of the DOM parent.
  4. The React object of the cache node is the Virtual DOM.


Part II :setState Update process:

I feel that the article is a little long, and I will explain it briefly:

Again, use transactions:

ReactComponent.prototype.setState = function (partialState, callback) {
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState'); }};Copy the code

Is this. See setState call updater. EnqueueSetState (this, partialState)

This. Updater is called in the ReactCompositeComponent instance based on the mount procedure above

MountComponent mounted


var ReactCompositeComponent = {
    construct: function(element) { //... Omit}, mountComponent:function(transaction, hostParent, hostContainerInfo, context) { //... var updateQueue = transaction.getUpdateQueue(); inst.props = publicProps; inst.context = publicContext; inst.refs = emptyObject; inst.updater = updateQueue; / / / /... }}Copy the code

Can see inst. Updater value transaction. GetUpdateQueue ();

The result is the exported object in ReactupDatequeue.js

var ReactUpdateQueue = {
   //....
  enqueueCallback: function (publicInstance, callback, callerName) {
    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
    if(! internalInstance) {return null;
    }
    if (internalInstance._pendingCallbacks) {
      internalInstance._pendingCallbacks.push(callback);
    } else {
      internalInstance._pendingCallbacks = [callback];
    }
    enqueueUpdate(internalInstance);
  },

  enqueueCallbackInternal: function (internalInstance, callback) {
    if (internalInstance._pendingCallbacks) {
      internalInstance._pendingCallbacks.push(callback);
    } else {
      internalInstance._pendingCallbacks = [callback];
    }
    enqueueUpdate(internalInstance);
  }
//....
}Copy the code


Start with the onClick registry highlighted in part 1 of the mount:

According to the first part of the document register click event callback function on is ReactEventListener dispatchEvent

So it’s triggered when you click



Here started to enter the batch update: ReactUpdates batchedUpdates (handleTopLevelImpl, bookKeeping);

Entered ReactDefaultBatchingStrategy transaction again

var ReactDefaultBatchingStrategy = {
  isBatchingUpdates: false,

  batchedUpdates: function (callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

    ReactDefaultBatchingStrategy.isBatchingUpdates = true;
    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
      returntransaction.perform(callback, null, a, b, c, d, e); }}};Copy the code





  • According to the transaction detail diagram above

    1. In the ReactDefaultBatchingStrategy
    • FLUSH_BATCHED_UPDATES. Initialize is empty
    • RESET_BATCHED_UPDATES. Initialize is empty

    Next, enter the function handleTopLevelImpl to execute

Find the queue of the corresponding parent component in the bubbling order from the native occurring, and loop through the processing



BookKeeping. Rooted list structure:



After some complicated upfront processing, the defined handleMessage is called




Then we call setSate(), passing in {className: “list-wrap”,message: “ha ha “} to the instance

In the HelloWorld _pendingStateQueue ReactCompositeComponentWrapper examples



Then call

function enqueueUpdate(internalInstance) {
  ReactUpdates.enqueueUpdate(internalInstance);
}Copy the code

function enqueueUpdate(component) {
    if(! batchingStrategy.isBatchingUpdates) { batchingStrategy.batchedUpdates(enqueueUpdate, component);return;
    }

    dirtyComponents.push(component);
    if(component._updateBatchNumber == null) { component._updateBatchNumber = updateBatchNumber + 1; }}Copy the code

Because this is in transaction batchingStrategy isBatchingUpdates = true



The component will be put in the dirtyComponents,

This is how React updates efficiently. When you’re in a transaction, no matter how many times you call setState, you only update it once, and all setState goes into dirtyComponents

The process is shown in the figure below:




This completes the handleTopLevelImpl in the transaction

The next step is to execute the transaction’s close method to update the content

  1. Back to ReactDefaultBatchingStrategy
  • FLUSH_BATCHED_UPDATES.close 

flushBatchedUpdates = function() {
    while (dirtyComponents.length || asapEnqueued) {
        if (dirtyComponents.length) {
            var transaction = ReactUpdatesFlushTransaction.getPooled();
            transaction.perform(runBatchedUpdates, null, transaction);
            ReactUpdatesFlushTransaction.release(transaction);
        }

        if (asapEnqueued) {
            asapEnqueued = false; var queue = asapCallbackQueue; asapCallbackQueue = CallbackQueue.getPooled(); queue.notifyAll(); CallbackQueue.release(queue); }}};Copy the code

The close method calls the flushBatchedUpdates method, which in turn contains a transaction

var transaction = ReactUpdatesFlushTransaction.getPooled();
    transaction.perform(runBatchedUpdates, null, transaction);Copy the code

The ReactUpdatesFlushTransaction transaction contains two pieces of the following:

var NESTED_UPDATES = {
    initialize: function() {
       this.dirtyComponentsLength = dirtyComponents.length;
    },
    close: function() {
        if(this.dirtyComponentsLength ! == dirtyComponents.length) { dirtyComponents.splice(0, this.dirtyComponentsLength); flushBatchedUpdates(); }else{ dirtyComponents.length = 0; }}}; var UPDATE_QUEUEING = { initialize:function() {
        this.callbackQueue.reset();
    },
    close: function() { this.callbackQueue.notifyAll(); }}; var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];Copy the code

The transaction execution diagram is posted below





According to the transaction detail diagram above

  1. In the ReactUpdatesFlushTransaction
  • NESTED_UPDATES. Initialization initialize / / this. DirtyComponentsLength number
  • UPDATE_QUEUEING. Initialize // Reset this.callbackQueue to store the code that needs to be updated

ReactReconcileTransaction affairs

  • Selection_restoration. initialize gets pre-update focus elements for post-update restoration (such as input focus)
  • EVENT_SUPPRESSION. Initialize, disables the event response
  • ON_DOM_READY_QUEUEING initializes the onDOMReady list













  • RESET_BATCHED_UPDATES. Close to reset isBatchingUpdates. IsBatchingUpdates = false flag updates to an end

Start runBatchedUpdates

function runBatchedUpdates(transaction) {
  var len = transaction.dirtyComponentsLength;
  dirtyComponents.sort(mountOrderComparator);

  for(var i = 0; i < len; I ++) {var Component = component [I]; Var callbacks = component._pendingCallbacks; component._pendingCallbacks = null; var markerName;if (ReactFeatureFlags.logTopLevelRenders) {
      var namedComponent = component;
      if(component._currentElement.props === component._renderedComponent._currentElement) { namedComponent = component._renderedComponent; }} / / execution updateComponent ReactReconciler. PerformUpdateIfNecessary (component, transaction. ReconcileTransaction); // Execute previously unexecuted callback in dirtyComponentif (callbacks) {
      for(var j = 0; j < callbacks.length; j++) { transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance()); }}}}Copy the code

The runBatchedUpdates loop iterates through the dirtyComponents array and does two main things. First execute performUpdateIfNecessary to refresh the component’s view, and then execute the previously blocked callback. Now let’s do formUpdate if Necessary.

performUpdateIfNecessary: function (transaction) {
    if(this._pendingElement ! = null) {// receiveComponent will eventually be called to updateComponent, To refresh the View ReactReconciler. ReceiveComponent (this, enclosing _pendingElement, transaction, enclosing _context); }if(this._pendingStateQueue ! = = null | | this. _pendingForceUpdate) {/ / perform updateComponent, to refresh the View. This.updatecomponent (Transaction, this._currentelement, this._currentelement, this._context, this._context); }}Copy the code



Began to call ReactCompositeComponentWrapper corresponding updateComponent HelloWorld example



Here if have componentWillReceiveProps calls the life cycle


Merge state changes for unified processing



We all know that custom components come in two forms:

class Greeting extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
或者
class Greeting extends React.PureComponent {
  render() {
    return<h1>Hello, {this.props.name}</h1>; }}Copy the code

React.PureComponent is very similar to react.component.react.component.react.pureComponent. The difference is that shouldComponentUpdate() is not implemented in react.pureComponent, which is implemented in a shallow way to compare prop and state.

ShouldComponentUpdate () in react.pureComponent is only a superficial comparison of objects


The code handling judgment is here:



Then go on:

this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext);

/ / ReactCompositeComponent _performComponentUpdate:function(nextElement, nextProps, nextState, nextContext, transaction, unmaskedContext) {
    var _this2 = this;

    var inst = this._instance;

    var hasComponentDidUpdate = Boolean(inst.componentDidUpdate);
    var prevProps;
    var prevState;
    var prevContext;
    if (hasComponentDidUpdate) {
        prevProps = inst.props;
        prevState = inst.state;
        prevContext = inst.context;
    }

    if (inst.componentWillUpdate) {
        if(process.env.NODE_ENV ! = ='production') {
            measureLifeCyclePerf(function() {
                return inst.componentWillUpdate(nextProps, nextState, nextContext);
            },
            this._debugID, 'componentWillUpdate');
        } else {
            inst.componentWillUpdate(nextProps, nextState, nextContext);
        }
    }

    this._currentElement = nextElement;
    this._context = unmaskedContext;
    inst.props = nextProps;
    inst.state = nextState;
    inst.context = nextContext;

    this._updateRenderedComponent(transaction, unmaskedContext);

    if (hasComponentDidUpdate) {
        if(process.env.NODE_ENV ! = ='production') {
            transaction.getReactMountReady().enqueue(function() {
                measureLifeCyclePerf(inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext), _this2._debugID, 'componentDidUpdate');
            });
        } else{ transaction.getReactMountReady().enqueue(inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext), inst);  }}}Copy the code



Call the componentWillUpdate life cycle





Coming up:

_updateRenderedComponent: function(transaction, context) { var prevComponentInstance = this._renderedComponent; var prevRenderedElement = prevComponentInstance._currentElement; Rendervalidatedcomponent (); renderValidatedComponent(); renderValidatedComponent(); // Determine whether to do DOM diff. React simplifies recursive diff by maintaining the same component hierarchy andtypeAnd key (key is used for components like listView, which we don't set in many cases)type), otherwise unmount and then re-mountif(shouldUpdateReactComponent(prevRenderedElement, NextRenderedElement)) {/ / recursive updateComponent, update the child components of Virtual DOM ReactReconciler. ReceiveComponent (prevComponentInstance, nextRenderedElement, transaction, this._processChildContext(context)); }else{ var oldNativeNode = ReactReconciler.getNativeNode(prevComponentInstance); // Do not do DOM diff, unload first, then load. Also is the first unMountComponent, again mountComponent ReactReconciler. UnMountComponent (prevComponentInstance,false); this._renderedNodeType = ReactNodeTypes.getType(nextRenderedElement); / / founded by ReactElement ReactComponent enclosing _renderedComponent = this. _instantiateReactComponent (nextRenderedElement); / / mountComponent mount components, get corresponding HTML component var nextMarkup = ReactReconciler. MountComponent (enclosing _renderedComponent, transaction, this._nativeParent, this._nativeContainerInfo, this._processChildContext(context)); // Insert HTML into the DOM this._replacenodeWithMarkup (oldNativeNode, nextMarkup, prevComponentInstance); }}Copy the code

Call the render method again:

Through shouldUpdateReactComponent (prevElement, nextElement)

Decide whether to do DOM diff or not

As in mountComponent, updateComponent updates child components recursively. The thing to watch out for here is DOM Diff. DOM Diff is the key to fast rendering in React. It will help us figure out what parts of the Virtual DOM actually change and perform native DOM operations on those parts. In order to avoid the inefficiency of cyclic recursive comparison nodes, React assumes that Virtual DOM updates are performed only on components with unchanged hierarchy, type, and key


There’s a need here



Summing up the setState process is complex and cleverly designed to avoid the need to repeatedly refresh components unnecessarily. Its main flow is as follows:

  1. EnqueueSetState queues the state and calls enqueueUpdate to process the Component to update
  2. If the Component is currently in an Update transaction, store the Component in the dirtyComponent first. Otherwise, the batchedUpdates handler is called.
  3. BatchedUpdates initiates a transaction.perform() transaction
  4. Transaction initialization starts, runs, and finishes
    1. Initialization: There are no methods registered during transaction initialization, so there are no methods to execute
    2. Run: The callback method passed in when setSate is executed, usually without passing a callback argument
    3. End: Update isBatchingUpdates to false and execute the close method in the wrapper for FLUSH_BATCHED_UPDATES
  5. FLUSH_BATCHED_UPDATES in the close phase, loop through all dirtyComponents, call updateComponent to refresh the component, And execute its pendingCallbacks, the callback set in setState.