In simple terms react---2 Interpretation of react source code

1.virtual dom model

Virtual DOM is like a virtual space to react. Almost all work of react is done based on virtual dom. Among them, the virtual DOM model is responsible for the construction of the underlying architecture of virtual dom. It has a complete set of virtual DOM tags, and is responsible for the construction, update and deletion of virtual nodes and their attributes. So,

1. How does the virtual DOM model build virtual nodes?

2. How to update node attributes?

Build a set of simple Virtual DOM The model is not complex, it only needs to have one DOM The basic elements required by the label are sufficient
:1.Tag name 2.Node attributes, including styles, attributes, events, etc.Child node 4. Apparent recognition id
 The example code is as follows:
{
  // Tag name
  tagName: 'div',
  // attribute
  properties: {
    style: {}
  },
  // Child node
  children: [],
  // Unique identification
  key: 1,
}

The nodes in the Virtual DOM are called reactnodes, which are divided into three types: ReactElement, ReactFragment and ReactText Among them, ReactElement is divided into ReactComponentElement and ReactDOMElement

1. Create React element

The virtual element created through jsx will eventually be compiled into the createElement method calling React. So what exactly does the createElement method do? Let's interpret the relevant source code (source code path: / v15.0.0/src/isomorphic/classic/element/ReactElement.js):

The Virtual DOM model creates virtual elements through createElement.

// createElement only makes a simple parameter correction and returns a ReactElement instance object, 
// That is, an instance of a virtual element
ReactElement.createElement = function (type, config, children) {
  // Initialization parameters
  var propName;
  var props = {};
  var key = null;
  var ref = null;
  var self = null;
  var source = null;
  // If config exists, extract its contents 
  if (config != null) {
    ref = config.ref === undefined ? null : config.ref;
    key = config.key === undefined ? null : '' + config.key;
    self = config.__self === undefined ? null : config.__self; source = config.__source === undefined ? null : config.__source;
    // Copy the contents of config to props (such as id and className)
    for (propName in config) {
      if (config.hasOwnProperty(propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
        props[propName] = config[propName];
      }
    }
  }
  // Handle children and mount them all on the children attribute of props. If there is only one parameter, it is directly assigned to children, 
  // Otherwise, merge
  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 a prop is empty and there is a default prop, the default prop is assigned to the current prop
  if (type && type.defaultProps) {
    var defaultProps = type.defaultProps; for (propName in defaultProps) {
      if (typeof props[propName] === 'undefined') {
        props[propName] = defaultProps[propName];
      }
    }
  }
  // Returns a ReactElement instance object
  return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
};

2. Initialize component entry

When using react to create a component, it will first call instantiateReactComponent, which is the entry function of initializing the component. It distinguishes the entries of different components by judging the node type.

1. When the node is empty, it indicates that the node does not exist, then initialize the empty group reactemptycomponent create(instantiateReactComponent)

2. When the node type is an object, that is, a dom tag component or a custom component, if the element type is a string, initialize the dom tag component reactnativecomponent Createinternalcomponent (element), otherwise initialize the custom component ReactCompositeComponentWrapper().

3. When the node type is string / number, initialize the text component reactnativecomponent create InstanceForText(node)

4. If it is other circumstances, it will not be handled.

The source code of the instantiateReactComponent method is as follows (source path: / v15.0.0/src/renderers/shared)/

reconciler/instantiateReactComponent.js):

// Initialize the component entry var instance;
// Empty component (ReactEmptyComponent)
function instantiateReactComponent(node, parentCompositeType) {
  var instance;
  if (node === null || node === false) {
    instance = ReactEmptyComponent.create(instantiateReactComponent);
  }
  if (typeof node === 'object') {
    var element = node;
    if (typeof element.type === 'string') {
      // DOM tag (ReactDOMComponent)
      instance = ReactNativeComponent.createInternalComponent(element);
    } else if (isInternalComponentType(element.type)) {
      // Custom components not represented by strings cannot be used temporarily. Component initialization will not be performed here
      instance = new element.type(element);
    } else {
      // Custom component (reactcomponentcomponent)
      instance = new ReactCompositeComponentWrapper();
    }
  } else if (typeof node === 'string' || typeof node === 'number') {
    // String or number (ReactTextComponent)
    instance = ReactNativeComponent.createInstanceForText(node);
  } else {
    // No treatment
  }
  // Set instance 
  instance.construct(node);
  // Initialization parameters
  instance._mountIndex = 0; instance._mountImage = null;
  return instance;
}

3. Text component

When the node type is a text node, it is not a Virtual DOM element, but React encapsulates it as a text component ReactDOMTextComponent in order to maintain the consistency of rendering.

When the mountComponent method is executed, ReactDOMTextComponent passes through transaction Usecreateelement determines whether the text is a node created through the createElement method. If so, create a corresponding label and ID domID for the node, so that each text node can have its own unique id like other React nodes, and also has the right of Virtual DOM diff. However, if the text is not created through createElement, React will no longer create < span > and domID IDs for it, but directly return the text content.

When the receiveComponent method is executed, you can use domchildrenoperations replaceDelimitedText

(commentNodes[0], commentNodes[1], nextStringText) to update the text content.

Source code of ReactDOMTextComponent (source code path): / v15.0.0/src/renderers/dom/shared/ReactDOM-

TextComponent.js) as follows:

// Create a text component. This is ReactText, not ReactElement
var ReactDOMTextComponent = function (text) {
  // Save the current string 
  this._currentElement = text;
  this._stringText = '' + text;
  // Parameters required for ReactDOMComponentTree 
  this._nativeNode = null;
  this._nativeParent = null;
  // attribute
  this._domID = null;
  this._mountIndex = 0;
  this._closingComment = null;
  this._commentNodes = null;
};
Object.assign(ReactDOMTextComponent.prototype, {
  mountComponent: function (transaction, nativeParent, nativeContainerInfo, context) {
    var domID = nativeContainerInfo._idCounter++;
    var openingValue = ' react-text: ' + domID + ' '; var closingValue = ' /react-text ';
    this._domID = domID;
    this._nativeParent = nativeParent;
    // If you create a text label using createElement, the text is tagged with a domID 
    if (transaction.useCreateElement) {
      var ownerDocument = nativeContainerInfo._ownerDocument;
      var openingComment = ownerDocument.createComment(openingValue);
      var closingComment = ownerDocument.createComment(closingValue);
      var lazyTree = DOMLazyTree(ownerDocument.createDocumentFragment()); // Start label
      DOMLazyTree.queueChild(lazyTree, DOMLazyTree(openingComment));
      // If it is a text type, create a text node
      if (this._stringText) {
        DOMLazyTree.queueChild(lazyTree, DOMLazyTree(ownerDocument.createTextNode(this._stringText)));
      }
      // End tag
      DOMLazyTree.queueChild(lazyTree, DOMLazyTree(closingComment)); ReactDOMComponentTree.precacheNode(this, openingComment);
      this._closingComment = closingComment;
      return lazyTree;
    } else {
      var escapedText = escapeTextContentForBrowser(this._stringText); // Directly return text under static page
      if (transaction.renderToStaticMarkup) {
        return escapedText;
      }
      // If the text is not created through createElement, the label and attribute will be commented out and the text content will be returned directly 
      return (
        '<!--' + openingValue + '-->' + escapedText +
        '<!--' + closingValue + '-->');
    }
  },


  // Update text content
  receiveComponent: function (nextComponent, transaction) {
    if (nextText !== this._currentElement) {
      this._currentElement = nextText;
      var nextStringText = '' + nextText;
      if (nextStringText !== this._stringText) {
        this._stringText = nextStringText;
        var commentNodes = this.getNativeNode();
        DOMChildrenOperations.replaceDelimitedText(commentNodes[0], commentNodes[1], nextStringText);
      }
    }
  },
});

4.DOM label component

The processing of Virtual DOM tags is mainly divided into the following two parts:

1. Attribute update, including update style, update attribute, event handling, etc;

When the mountComponent method is executed, ReactDOMComponent first generates tags and labels through this Createopentagmarkupandputlisteners (transaction) to handle the properties and events of DOM nodes

  1. If there is an event, add an event agent for the current node, that is, call enqueuePutListener(this, propKey, propValue, transaction).
  2. If there are styles, merge them first Assign ({}, props. Style), and then through csspropertyoperations Createmarkupforstyles (propvalue, this) creates a style.
  3. Through dompropertyoperations Createmarkupforproperty (propkey, propvalue) creates a property.
  4. Through dompropertyoperations Createmarkupforid (this.).
  5. If there is an event, add an event agent for the current node, that is, call enqueuePutListener(this, propKey, propValue, transaction).
  6. If there are styles, merge them first Assign ({}, props. Style), and then through csspropertyoperations Createmarkupforstyles (propvalue, this) creates a style.
  7. Through dompropertyoperations Createmarkupforproperty (propkey, propvalue) creates a property.
  8. Through dompropertyoperations Createmarkupforid (this.).
_createOpenTagMarkupAndPutListeners: function(transaction, props) {
  var ret = '<' + this._currentElement.type;
  // Piece together attributes
  for (var propKey in props) {
    var propValue = props[propKey];
    if (registrationNameModules.hasOwnProperty(propKey)) { // Add an event agent for the current node
      if (propValue) {
        enqueuePutListener(this, propKey, propValue, transaction);
      }
    } else {
      if (propKey === STYLE) {
        if (propValue) {
          // Merge style
          propValue = this._previousStyleCopy = Object.assign({}, props.style);
        }
        propValue = CSSPropertyOperations.createMarkupForStyles(propValue, this);
      }
      // Create attribute ID
      var markup = null;
      if (this._tag != null && isCustomComponent(this._tag, props)) {
        markup = DOMPropertyOperations.createMarkupForProperty(propKey, propValue);
      }
      if (markup) {
        ret += ' ' + markup;
      }
    }
  }
  // For static pages, there is no need to set react ID, which can save a lot of bytes if (transaction.renderToStaticMarkup){
  return ret;
}
// Set react ID
if (!this._nativeParent) {
  ret += ' ' + DOMPropertyOperations.createMarkupForRoot();
}
ret += ' ' + DOMPropertyOperations.createMarkupForID(this._domID);
return ret
}

When executed receiveComponent Method, ReactDOMComponent Will pass this.updateComponent (transaction, prevElement, nextElement, context) To update DOM Node attributes.

First, delete the old attributes you don't need. If the old style is not needed, traverse the old style collection and empty and delete each style;If no event is needed, the event listening attribute will be removed, that is, the event agent will be canceled for the current node deleteListener(this, propKey);If the old attribute is not in the new attribute set, you need to delete the old attribute DOMPropertyOperations.deleteValueForProperty(getNode(this), propKey). 

Then update the new attributes. If a new style exists, the new style is merged Object.assign({}, nextProp);Clear the style if it is in the old style but not in the new style;If you are in both the old style and the new style and are different, update the style styleUpdates[styleName] = nextProp[styleName];If you are in the new style, but not in the old style, update to the new style directly styleUpdates = nextProp;If there is an event update, add the attribute of event listening enqueuePutListener(this, propKey, nextProp, transaction);If a new attribute exists, add a new attribute or update the old attribute with the same name DOMPropertyOperations.setValueForAttribute(node, propKey,
nextProp). 
So far, ReactDOMComponent It's done DOM The relevant codes of node attribute update are as follows

function _updateDOMProperties(lastProps, nextProps, transaction) {
  var propKey;
  var styleName;
  var styleUpdates;
  // When an old attribute is not in the new attribute set, it needs to be deleted
  for (propKey in lastProps) {
    // If there is in the new attribute or the propKey is on the prototype, skip it directly, so that the rest are not in the new attribute set,
    // Need to delete
    if (nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey) || lastProps[propKey] == null) {
      continue;
    }
    // Removes unwanted styles from the DOM 
    if (propKey === STYLE) {
      var lastStyle = this._previousStyleCopy; for (styleName in lastStyle) {
        if (lastStyle.hasOwnProperty(styleName)) {
          styleUpdates = styleUpdates || {}; styleUpdates[styleName] = '';
        }
      }
      this._previousStyleCopy = null;
    } else if (registrationNameModules.hasOwnProperty(propKey)) {
      if (lastProps[propKey]) {
        // The event listening attribute here needs to remove listening and cancel the event agent for the current node
        deleteListener(this, propKey);
      }
    } else if (DOMProperty.isStandardName[propKey] || DOMProperty.isCustomAttribute(propKey)) {
      // Remove unwanted attributes from the DOM 
      DOMPropertyOperations.deleteValueForProperty(getNode(this), propKey);
    }
  }

  // The new attribute needs to be written to the DOM node 
  for (propKey in nextProps) {
    var nextProp = nextProps[propKey]; var lastProp = propKey === STYLE ? this._previousStyleCopy : lastProps != null ? lastProps[propKey] : undefined;
    // If it is not in the new attribute or is the same as the old attribute, skip
    if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp || nextProp == null && lastProp == null) {
      continue;
    }
    // Write a new style on the DOM (Update Style) 
    if (propKey === STYLE) {
      if (nextProp) {
        nextProp = this._previousStyleCopy = Object.assign({}, nextProp);
      }
      if (lastProp) {
        // In the old style and not in the new style, clear the style
        for (styleName in lastProp) {
          if (lastProp.hasOwnProperty(styleName) && (!nextProp || !nextProp.hasOwnProperty(styleName))) {
            styleUpdates = styleUpdates || {}; styleUpdates[styleName] = '';
          }
        }
        // Update the style if it is not the same in both the old style and the new style
        for (styleName in nextProp) {
          if (nextProp.hasOwnProperty(styleName) && lastProp[styleName] !== nextProp[styleName]) {
            styleUpdates = styleUpdates || {};
            styleUpdates[styleName] = nextProp[styleName];
          }
        }
      } else {
        // There is no old style. Write the new style directly 
        styleUpdates = nextProp;
      }
    } else if (registrationNameModules.hasOwnProperty(propKey)) {
      if (nextProp) {
        // Add event listening properties
        enqueuePutListener(this, propKey, nextProp, transaction);
      } else {
        deleteListener(this, propKey);
      }
      // Add a new property or update an old property with the same name
    } else if (isCustomComponent(this._tag, nextProps)) {
      if (!RESERVED_PROPS.hasOwnProperty(propKey)) {
        // setValueForAttribute update attribute 
        DOMPropertyOperations.setValueForAttribute(getNode(this), propKey, nextProp);
      }
    } else if (DOMProperty.properties[propKey] || DOMProperty.isCustomAttribute(propKey)) {
      var node = getNode(this); if (nextProp != null) {
        DOMPropertyOperations.setValueForProperty(node, propKey, nextProp);
      } else {
        // If the update is null or undefined, delete the property
        DOMPropertyOperations.deleteValueForProperty(node, propKey);
      }
    }
    // If styleUpdates is not empty, set a new style
    if (styleUpdates) {
      CSSPropertyOperations.setValueForStyles(getNode(this), styleUpdates, this);
    }
  }
}

2. Update of child nodes, including update content and update of child nodes. This part involves diff algorithm.

When executed mountComponent Method, ReactDOMComponent Will pass this._createContentMarkup (transaction, props, context) To handle dom The content of the node.
First, get the node content props.dangerouslySetInnerHTML. If there are child nodes, pass this. mountChildren(childrenToUse, transaction, context) Initial rendering of child nodes:
_createContentMarkup: function(transaction, props, context) {
  var ret = '';
  // Get the content rendered by child nodes
  var innerHTML = props.dangerouslySetInnerHTML;
  if (innerHTML != null) {
    if (innerHTML.__html != null) {
      ret = innerHTML.__html;
    }
  } else {
    var contentToUse = CONTENT_TYPES[typeof props.children] ? props.children : null; var childrenToUse = contentToUse != null ? null : props.children;
    if (contentToUse != null) {
      ret = escapeTextContentForBrowser(contentToUse);
    } else if (childrenToUse != null) {
      // Initialize rendering of child nodes
      var mountImages = this.mountChildren(childrenToUse, transaction, context);
      ret = mountImages.join('');
    }
  }
  // Need to wrap
  if (newlineEatingTags[this._tag] && ret.charAt(0) === '\n') {
    return '\n' + ret;
  } else {
    return ret;
  }
}
When executed receiveComponent Method, ReactDOMComponent Will pass this._updateDOMChildren
(lastProps, nextProps, transaction, context) To update DOM Content and child nodes.

First, delete unnecessary child nodes and contents. If the old node exists and the new node does not exist, it means that the current node is deleted after updating. At this time, execute the method this.updateChildren(null, transaction, context);If the old content exists and the new content does not exist, it means that the current content is deleted after updating. At this time, execute the method this.updateText- Content(''). 

Then update the child nodes and contents. If the new child node exists, update its child node and execute the method at this time this.update- Children(nextChildren, transaction, context);If the new content exists, the content is updated and the method is executed this.updateTextContent('' + nextContent). 

So far, ReactDOMComponent It's done DOM The relevant codes for updating child nodes and contents are as follows:
_updateDOMChildren: function(lastProps, nextProps, transaction, context) {
  // initialization
  var lastContent = CONTENT_TYPES[typeof lastProps.children] ? lastProps.children : null;
  var nextContent = CONTENT_TYPES[typeof nextProps.children] ? nextProps.children : null;
  var lastHtml = lastProps.dangerouslySetInnerHTML && lastProps.dangerouslySetInnerHTML.__html; var nextHtml = nextProps.dangerouslySetInnerHTML && nextProps.dangerouslySetInnerHTML.__html;
  var lastChildren = lastContent != null ? null : lastProps.children; var nextChildren = nextContent != null ? null : nextProps.children; var lastHasContentOrHtml = lastContent != null || lastHtml != null; var nextHasContentOrHtml = nextContent != null || nextHtml != null;
  if (lastChildren != null && nextChildren == null) {
    // The old node exists but the new node does not exist, indicating that the current node has been deleted after updating
     this.updateChildren(null, transaction, context);
  } else if (lastHasContentOrHtml && !nextHasContentOrHtml) { // Indicates that the current content has been deleted after updating 
    this.updateTextContent('');
  }
  // New node exists
  if (nextContent != null) {
    // Update content
    if (lastContent !== nextContent) {
      this.updateTextContent('' + nextContent);
    }
  } else if (nextHtml != null) {
    // Update attribute ID
    if (lastHtml !== nextHtml) {
      this.updateMarkup('' + nextHtml);
    }
  } else if (nextChildren != null) {
    // Update child nodes
    this.updateChildren(nextChildren, transaction, context);
  }
}

When uninstalling components, ReactDOMComponent A series of operations will be carried out, such as unloading child nodes, clearing event listening, clearing IDS, etc:
unmountComponent: function(safely) {
  this.unmountChildren(safely);
  ReactDOMComponentTree.uncacheNode(this); EventPluginHub.deleteAllListeners(this); ReactComponentBrowserEnvironment.unmountIDFromEnvironment(this._rootNodeID); this._rootNodeID = null;
  this._domID = null;
  this._wrapperState = null;
}

 

ReactDOMComponent diagram

5. Custom components

Custom components implement a complete set of react life cycle and setState mechanism. Therefore, custom components update attributes, contents and child nodes in the environment of life cycle. These update operations are similar to those of ReactDOMComponent.

2. Life cycle management art

https://juejin.cn/post/6844904199923187725#heading-11

3. Decrypt setState mechanism

When this When setstate() is called, React will call the render method again to re render the UI.

1. Asynchronous update of setstate

setState implements state update through a queue mechanism. When setState is executed, the states that need to be updated will be merged and put into the state queue instead of updating this immediately State, the queue mechanism can efficiently update state in batches. If you modify this directly without using setState State value, then the state will not be put into the status queue. When setState is called next time and the status queue is merged, the previously modified state will be ignored, resulting in unpredictable errors. Therefore, the setState method should be used to update the state. At the same time, react uses the state queue mechanism to realize the asynchronous update of setState and avoid frequent repeated update of state.

Relevant codes are as follows:

// Merge the new state into the status update queue

var nextState = this._processPendingState(nextProps, nextContext);

// Judge whether to update components according to the status of update queue and shouldComponentUpdate

var shouldUpdate = this._pendingForceUpdate ||

  !inst.shouldComponentUpdate || inst.shouldComponentUpdate(nextProps, nextState, nextContext);

2. Risk of setstate loop call

When setState is called, the enqueueSetState method is actually executed, and the partialState and pendingStateQueue update queues are merged. Finally, the state update is executed through enqueueUpdate. When setState is called, the enqueueSetState method is actually executed and the partialState and_ Pending forceupdate, and call the receiveComponent and updateComponent methods to update the components.

If you call setState in the shouldComponentUpdate or componentWillUpdate method, then this._pendingStateQueue != null, the performUpdateIfNecessary method will call the updateComponent method to update the component, but the updateComponent method will call the shouldcomponentupdate and componentWill- Update methods, which will cause a circular call and crash after the browser memory is full

setState source code:

// Update state
ReactComponent.prototype.setState = function (partialState, callback) {
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};
enqueueSetState: function(publicInstance, partialState) {
  var internalInstance = getInternalInstanceReadyForUpdate(
    publicInstance,
    'setState');
  if (!internalInstance) {
    return;
  }
  // Update queue merge operation
  var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
  queue.push(partialState);
  enqueueUpdate(internalInstance);
},
// If it exists_ pendingElement,_ pendingStateQueue and_ pendingForceUpdate, the component is updated 
performUpdateIfNecessary: function(transaction) {
  if (this._pendingElement != null) {
    ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
  }
  if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
    this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
  }
}

3.setState call stack

setState is updated by enqueueUpdate. How does enqueueUpdate update update the state? What exactly did enqueueUpdate do?

The code of enqueueUpdate is as follows (source code path: / v15.0.0/src/ renderers/shared/reconciler/ReactUpdates

js)

function enqueueUpdate(component) {
  ensureInjected();
  // If not in batch update mode
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }
  // If you are in batch update mode, save the component in dirtyComponents
  dirtyComponents.push(component);
}

If isBatchingUpdates by true,Updates in multiple queues are executed batchedUpdates Method, otherwise only the current component will be put into dirtyComponents Array.
batchingStrategy What did you do?
In fact, it is just a simple object that defines a isBatchingUpdates Boolean value of, and batchedUpdates method(Source path:/v15.0.0/src/renderers/shared/ reconciler/ReactDefaultBatchingStrategy.js):
var ReactDefaultBatchingStrategy = {
  isBatchingUpdates: false,
  batchedUpdates: function (callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
    ReactDefaultBatchingStrategy.isBatchingUpdates = true;
    if (alreadyBatchingUpdates) {
      callback(a, b, c, d, e);
    } else {
      // affair
      transaction.perform(callback, null, a, b, c, d, e);
    }
  },
}

setState simplified call stack

4. Acquaintance affairs

Definition: provide a reliable recovery mechanism to ensure the consistency of data in case of error, and different transactions are independent of each other.

Transaction flow chart:

Transaction is to encapsulate the methods to be executed with wrapper, and then execute them through the perform method provided by the transaction. Before performing, execute the initlallize method in all wrappers. After performing perfom, execute all the close methods. A set of initialize and close methods is called a wrapper.

Transactions support multiple wrapper overlays.

Simple transaction case:

var Transaction = require('./Transaction');
// Our own defined transactions
var MyTransaction = function () {
  // . . . 
}

Object.assign(MyTransaction.prototype, Transaction.Mixin, {
  getTransactionWrappers: function () {
    return [{
      initialize: function () {
        console.log('before method perform');
      },
      close: function () {
        console.log('after method perform');
      }
    }];
  }
});
var transaction = new MyTransaction();
var testMethod = function () {
  console.log('test');
}
transaction.perform(testMethod);
  // The printed results are as follows:
  // before method perform // test
  // after method perform

5. Decrypt setState

The whole process of rendering the react component into the dom is in a large transaction.

4.diff algorithm

react the process of converting a Virtual DOM tree into an actual DOM tree is called reconciliation. diff algorithm is the concrete realization of reconciliation.

Three strategies of diff algorithm:

  1. Strategy 1 In the Web UI, there are few cross level mobile operations of DOM nodes, which can be ignored.
  2. Strategy 2 Two components with the same class will generate a similar tree structure, and two components with different classes will generate different tree structures.
  3. Strategy 3 For a group of child nodes at the same level, they can be distinguished by unique id.

Based on the above strategies, react optimizes the algorithms of tree diff, component diff and element diff respectively. The facts also prove that these three premise strategies are reasonable and accurate, which ensures the overall interface construction and performance.

     1.tree diff

1: Based on Strategy 1, react optimizes the tree algorithm succinctly, that is to compare the trees in layers. The two trees will only compare the nodes in the same layer. Since the cross level movement operations of dom nodes are negligible, react controls the Virtual DOM tree through updateDepth. It only compares dom nodes at the same level, that is, all child nodes under the same parent node. When it is found that the node no longer exists, the node and its child nodes are completely deleted and will not be used for further comparison. In this way, you only need to traverse the tree once to complete the comparison of the whole dom tree.

2: updateChildren The source code corresponding to the method is as follows:
updateChildren: function(nextNestedChildrenElements, transaction, context) {
  updateDepth++;
  var errorThrown = true;
  try {
    this._updateChildren(nextNestedChildrenElements, transaction, context);
    errorThrown = false;
  } finally {
    updateDepth--;
    if (!updateDepth) {
      if (errorThrown) {
        clearQueue();
      } else {
        processQueue();
      }
    }
  }
}

3: If DOM nodes move across levels, how will diff behave?

As shown in the figure above, if node a (including its children) is moved to node D, react will simply consider the position transformation of nodes at the same level, while nodes at different levels will only be created and deleted. When the root node finds that a in the child nodes disappears, it will directly destroy A. when D finds that there is an additional child node a, it will create a new a (including child nodes) as its child nodes.

At this time, the execution of diff: create A → create B → create C → delete A.

It can be found that when A node moves across levels, the imaginary move operation will not occur, but the whole tree with A as the root node is recreated. This is an operation that affects the performance of React. Therefore, it is officially recommended not to perform cross level operations on DOM nodes.

    2.component diff

    1. If it is a component of the same type, continue to compare the Virtual DOM tree according to the policy.

     2. If not, judge the component as dirty component, so as to replace all child nodes under the whole component.

     3. For components of the same type, there may be no change in their Virtual DOM. If you can know this exactly, you can save a lot of diff operation time. Therefore, react allows users to judge whether the component needs diff algorithm analysis through shouldComponentUpdate().

As shown in the figure above, when component D becomes component g, even if the structures of the two components are similar, once react judges that D and G are different types of components, it will not compare their structures, but directly delete component D and recreate component g and its child nodes. Although diff will affect the performance when the two components are of different types and have similar structures, as stated in the official react blog, there are few similar dom trees in different types of components, so this extreme factor is difficult to have a significant impact in the actual development process.

3.element diff

   1. When nodes are at the same level, diff provides three node operations: INSERT_MARKUP, MOVE_EXISTING and remove_node

1. Insert. The new component type is not in the old collection, that is, a new node. You need to insert the new node.

2. Move. There are new component types in the old collection, and the element is an updatable type. generateComponent- Children have called receiveComponent. In this case, prevChild=nextChild, you need to move and reuse the previous dom nodes.

3. Delete. The old component types also exist in the new set, but the corresponding element s are different, they cannot be reused and updated directly, and they need to be deleted, or if the components are no longer in the new set, they also need to be deleted.

Relevant codes are as follows:

function makeInsertMarkup(markup, afterNode, toIndex) {
  return {
    type: ReactMultiChildUpdateTypes.INSERT_MARKUP, content: markup,
    fromIndex: null,
    fromNode: null,
    toIndex: toIndex,
    afterNode: afterNode,
  };
}

function makeMove(child, afterNode, toIndex) {
  return {
    type: ReactMultiChildUpdateTypes.MOVE_EXISTING,
    content: null,
    fromIndex: child._mountIndex,
    fromNode: ReactReconciler.getNativeNode(child), toIndex: toIndex,
    afterNode: afterNode
  };
}

function makeRemove(child, node) {
  return {
    type: ReactMultiChildUpdateTypes.REMOVE_NODE,
    content: null,
    fromIndex: child._mountIndex,
    fromNode: node, toIndex: null,
    afterNode: null,
  };
}

Case analysis:

1. Move operation (there are the same nodes in the old and new sets, but the positions are different)

First, loop through the nodes in the new set for (name in nextChildren), and judge whether the same node if (prevChild === nextChild) exists in the old and new sets through the unique key. If the same node exists, move it, However, before moving, you need to compare the position of the current node in the old set with lastIndex if (child.). lastIndex is always updated, indicating the rightmost position (i.e. the largest position) of the visited node in the old collection. If the current access node in the new set is larger than lastIndex, it means that the current access node is located later than the previous node in the old set, and the node will not affect the position of other nodes. Therefore, it does not need to be added to the difference queue, that is, it does not perform the move operation. The move operation is required only when the accessed node is smaller than lastIndex.

As shown in the figure above, diff difference comparison process:

1. Get B from the new set, and then judge whether there is the same node B in the old set. At this time, it is found that there is node B, and then judge whether to move by comparing the node position. B's position in the old set B_ Mountindex = 1, lastIndex = 0 at this time, which does not meet the requirement of child_ The condition of mountindex < lastindex, so B is not moved. Update lastindex = math Max (prevchild. _mountindex, lastindex), where prevchild_ Mountindex indicates the position of B in the old set, then lastIndex = 1 and updates the position of B to prevchild in the new set_ Mountindex = nextIndex, B_ Mountindex = 0, nextIndex + + enters the judgment of the next node.

 

2. Obtain a from the new set, and then judge whether there is the same node a in the old set. At this time, it is found that there is node a, and then judge whether to move by comparing the node position. A's position in the old set a_ Mountindex = 0, lastIndex = 1 at this time, meeting the requirements of child_ The condition of mountindex < lastindex, so move a enqueueMove(this, child._mountIndex, toIndex), where toIndex is actually nextIndex, indicating the position a needs to move to. Update lastindex = math Max (prevchild. _mountindex, lastindex), then lastIndex = 1, and update the position of a to prevchild in the new set_ Mountindex = nextIndex, a_ Mountindex = 1, nextIndex + + enters the judgment of the next node.

 

3. Obtain d from the new set, and then judge whether there is the same node D in the old set. At this time, it is found that there is node D, and then judge whether to move by comparing the node position. D's position in the old set D_ Mountindex = 3, this

When lastIndex = 1, it does not meet the requirement of child_ Mountindex < lastindex, so D is not moved. Update lastindex = math Max (prevchild. _mountindex, lastindex), then lastIndex = 3, and update the position of D to prevchild in the new set_ Mountindex = nextIndex. At this time, D_ Mountindex = 2, nextIndex + + enters the judgment of the next node.

 

4. Obtain C from the new set, and then judge whether there is the same node C in the old set. At this time, it is found that there is node C, and then judge whether to move by comparing the node position. C's position in the old set C_ Mountindex = 2, this

When lastIndex = 3, it meets the requirements of child_ Mountindex < lastindex, so move C enqueueMove(this, child._mountIndex, toIndex). Update lastindex = math Max (prevchild. 7 _mountindex, lastindex), then lastIndex = 3, and update the position of C to prevchild in the new set_ Mountindex = nextIndex, a_ Mountindex = 3, nextIndex + + enters the judgment of the next node. Since C is the last node, the diff operation is completed here.

2. Create, move and delete (there are newly added nodes in the new set and there are nodes to be deleted in the old set)

diff process:

1. Get B from the new set, and then judge whether the same node B exists in the old set. You can find that node B exists. Because of B's position in the old set, B_ Mountindex = 1, lastIndex = 0 at this time, so B will not be moved. Update lastIndex = 1 and update the position of B to position B in the new set_ Mountindex = 0, nextIndex + + enters the judgment of the next node.

 

2. Obtain E from the new set, and then judge whether the same node E exists in the old set. It can be found that it does not exist. At this time, a new node E can be created. Update lastIndex = 1, update the position of E to the position in the new set, and nextIndex + + enters the judgment of the next node.

 

3. Obtain C from the new set, and then judge whether the same node C exists in the old set. At this time, node C can be found. Because of C's position in the old set_ Mountindex = 2, lastIndex = 1, C_ Mountindex > lastindex, so C is not moved. Update lastIndex = 2, update the position of C to the position in the new set, and nextIndex + + enters the judgment of the next node.

 

4. Obtain A from the new set, and then judge whether the same node A exists in the old set. At this time, node A is found. Due to the position of A in the old set, A_ Mountindex = 0, lastIndex = 2, A_ Mountindex < lastindex, so move A. Update lastIndex = 2, update the position of A to the position in the new set, and nextIndex + + enters the judgment of the next node.

 

5. After completing the differentiation comparison of all nodes in the new set, it is also necessary to cycle through the old set to judge whether there are nodes that do not exist in the new set but still exist in the old set. At this time, it is found that such node D exists, so delete node D and the diff operation is completed.

 

The relevant codes are as follows (source path: / v15.0.0/src/renderers/shared/reconciler/ReactMultiChild.js):

function _updateChildren(nextNestedChildrenElements, transaction, context) {
  var prevChildren = this._renderedChildren;
  var removedNodes = {};
  var nextChildren = this._reconcilerUpdateChildren(prevChildren, nextNestedChildrenElements,
    removedNodes, transaction, context);
  // If prevChildren and nextChildren do not exist, diff processing will not be performed 
  if (!nextChildren && !prevChildren) {
    return;
  }
  var updates = null;
  var name;
  // lastIndex is the last index in prevChildren, and nextIndex is the index of each node in nextChildren 
  var lastIndex = 0;
  var nextIndex = 0;
  var lastPlacedNode = null;
  for (name in nextChildren) {
    if (!nextChildren.hasOwnProperty(name)) {
      continue;
    }
    var prevChild = prevChildren && prevChildren[name];
    var nextChild = nextChildren[name];
    if (prevChild = nextChild) {
      // Mobile node 
      updates = enqueue(updates, this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex));
      lastIndex = Math.max(prevChild._mountIndex, lastIndex);
      prevChild._mountIndex = nextIndex;
    } else {
      if (prevChild) {
        lastIndex = Math.max(prevChild._mountIndex, lastIndex);
        // Delete the child node prevChild by traversing removedNodes
      }
      // Initialize and create nodes 
      updates = enqueue(updates, this._mountChildAtIndex(nextChild, lastPlacedNode, nextIndex, transaction, context));
    }
    nextIndex++;
    lastPlacedNode = ReactReconciler.getNativeNode(nextChild);
  }
  // If the parent node does not exist, all its child nodes are removed 
  for (name in removedNodes) {
    if (removedNodes.hasOwnProperty(name)) {
      updates = enqueue(updates, this._unmountChild(prevChildren[name], removedNodes[name]));
    }
  }
  // If there are updates, the update queue is processed 
  if (updates) {
    processQueue(this, updates);
  }
  this._renderedChildren = nextChildren;
}
function enqueue(queue, update) {
  // If there is any update, save it in the queue 
  if (update) {
    queue = queue || [];
    queue.push(update);
  }
  return queue;
}
// Processing queue updates
function processQueue(inst, updateQueue) {
  ReactComponentEnvironment.processChildrenUpdates(inst,
    updateQueue,
  );
}
// Mobile node
function moveChild(child, afterNode, toIndex, lastIndex) {
  // If the index of the child node is less than lastIndex, move the node 
  if (child._mountIndex < lastIndex) {
    return makeMove(child, afterNode, toIndex);
  }
}
// Create node
function createChild(child, afterNode, mountImage) {
  return makeInsertMarkup(mountImage, afterNode, child._mountIndex);
}
// Delete node
function removeChild(child, node) {
  return makeRemove(child, node);
}
// Unload rendered child nodes 
function _unmountChild(child, node) {
  var update = this.removeChild(child, node); child._mountIndex = null;
  return update;
}
// Instantiate the child node with the name provided
function _mountChildAtIndex(child, afterNode, index, transaction, context) {
  var mountImage = ReactReconciler.mountComponent(child, transaction, this, this._nativeContainerInfo, context);
  child._mountIndex = index;
  return this.createChild(child, afterNode, mountImage);
}

 

5.React Patch method

1. Existing value: pach is to update the DOM difference queue calculated by tree diff to the real DOM node, and finally enable the browser to render the updated data. If there is no patch, it will be futile for react to perform more performance optimization operations based on Virtual DOM, because the browser does not know Virtual DOM

2. Implementation: it is mainly realized by traversing the difference queue. When traversing the difference queue, perform corresponding operations through the update type, including inserting new nodes, moving and deleting existing nodes, etc.

3. Why can I insert nodes directly in turn here? The reason is that when adding difference nodes to the difference queue in the diff phase, they are added in order. In other words, the order of new nodes (including move and insert) in the queue is the order of the final real DOM, so you can insert nodes directly according to the index in turn. Moreover, React does not execute the Patch once after calculating a difference, but after calculating all the differences and putting them into the difference queue, execute the Patch method once again to complete the update of the real dom.

4. The source code of the patch method is as follows (source path: / v15.0.0/src/renderers/dom/client/utils/DOMChildren- Operations.js):

function processUpdates(parentNode, updates) {
  //Handle new nodes, mobile nodes and nodes to be removed 
  for (var k = 0; k < updates.length; k++) {
    var update = updates[k];
    switch (update.type) {
      // Insert new node
      case ReactMultiChildUpdateTypes.INSERT_MARKUP:
        insertLazyTreeChildAt(
          parentNode,
          update.content,
          getNodeAfter(parentNode, update.afterNode)
        );
        break;
      // Nodes to be moved
      case ReactMultiChildUpdateTypes.MOVE_EXISTING:
        moveChild(
          parentNode,
          update.fromNode,
          getNodeAfter(parentNode, update.afterNode)
        );
        break;
      case ReactMultiChildUpdateTypes.SET_MARKUP:
        setInnerHTML(parentNode, update.content
        );
        break;
      case ReactMultiChildUpdateTypes.TEXT_CONTENT:
        setTextContent(parentNode, update.content
        );
        break;
      // Nodes to be deleted
      case ReactMultiChildUpdateTypes.REMOVE_NODE:
        removeChild(parentNode, update.fromNode);
        break;
    }
  }
}
function getNodeAfter(parentNode, node) {
  // The return format of the text component [open, close] comments needs special processing 
  if (Array.isArray(node)) {
    node = node[1];
  }
  return node ? node.nextSibling : parentNode.firstChild;
}
// Operation of inserting a new node
function insertLazyTreeChildAt(parentNode, childTree, referenceNode) {
  DOMLazyTree.insertTreeBefore(parentNode, childTree, referenceNode);
}
// Operation of moving existing nodes
function moveChild(parentNode, childNode, referenceNode) {
  if (Array.isArray(childNode)) {
    moveDelimitedText(parentNode, childNode[0], childNode[1], referenceNode);
  } else {
    insertChildAt(parentNode, childNode, referenceNode);
  }
}
// Remove existing nodes
function removeChild(parentNode, childNode) {
  if (Array.isArray(childNode)) {
    var closingComment = childNode[1];
    childNode = childNode[0];
    removeDelimitedText(parentNode, childNode, closingComment); parentNode.removeChild(closingComment);
  }
  parentNode.removeChild(childNode);
}
// The text component needs to remove openingComment and closengcomment and obtain the node
function moveDelimitedText(parentNode, openingComment, closingComment, referenceNode) {
  var node = openingComment; while (true) {
    var nextNode = node.nextSibling; insertChildAt(parentNode, node, referenceNode); if (node === closingComment) {
      break;
    }
    node = nextNode;
  }
}
function removeDelimitedText(parentNode, startNode, closingComment) {
  while (true) {
    var node = startNode.nextSibling; if (node === closingComment) {
      // Closencomment has been removed by ReactMultiChild
      break;
    } else {
      parentNode.removeChild(node);
    }
  }
}

6. React router principle

7.React Fiber

https://zhuanlan.zhihu.com/p/26027085

Keywords: React

Added by Rick Corbett on Fri, 28 Jan 2022 23:02:43 +0200