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
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
_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 | createReactEmptyComponent component |
object && type === string |
Virtual DOM | createReactDOMComponent component |
object && type ! == string |
The React components | createReactCompositeComponent component |
string |
string | createReactTextComponent component |
number |
digital | createReactTextComponent component |
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
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
- 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)
Perform first: putListener
Next :componentDidMount
- 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
- 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.
- 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.
- _mountImageIntoNode(), inserts the HTML into the DOM parent by setting the innerHTML property of the DOM parent.
- 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
- 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
- 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
According to the transaction detail diagram above
- 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:
- EnqueueSetState queues the state and calls enqueueUpdate to process the Component to update
- If the Component is currently in an Update transaction, store the Component in the dirtyComponent first. Otherwise, the batchedUpdates handler is called.
- BatchedUpdates initiates a transaction.perform() transaction
- Transaction initialization starts, runs, and finishes
- Initialization: There are no methods registered during transaction initialization, so there are no methods to execute
- Run: The callback method passed in when setSate is executed, usually without passing a callback argument
- End: Update isBatchingUpdates to false and execute the close method in the wrapper for FLUSH_BATCHED_UPDATES
- 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.