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
- If there is an event, add an event agent for the current node, that is, call enqueuePutListener(this, propKey, propValue, transaction).
- If there are styles, merge them first Assign ({}, props. Style), and then through csspropertyoperations Createmarkupforstyles (propvalue, this) creates a style.
- Through dompropertyoperations Createmarkupforproperty (propkey, propvalue) creates a property.
- Through dompropertyoperations Createmarkupforid (this.).
- If there is an event, add an event agent for the current node, that is, call enqueuePutListener(this, propKey, propValue, transaction).
- If there are styles, merge them first Assign ({}, props. Style), and then through csspropertyoperations Createmarkupforstyles (propvalue, this) creates a style.
- Through dompropertyoperations Createmarkupforproperty (propkey, propvalue) creates a property.
- 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:
- Strategy 1 In the Web UI, there are few cross level mobile operations of DOM nodes, which can be ignored.
- 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.
- 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); } } }