Writing in the front
React has been in development for more than a year. Recently, I studied the React source code carefully. React source code is complex and not suitable for beginners. Therefore, this article implements a simple version of React to make it easier to understand the principle (this article is based on React V15). Include:
- React components and the first rendering implementation
- React update mechanism implementation and React Diff algorithm
React code is still quite complex, although here’s a simplified version. But you still need to have good object-oriented thinking. React has several key points at its core.
- Virtual DOM Objects
- Diff Algorithm for Virtual DOM Differentiation
- Unidirectional data flow
- Component declaration cycle
- The event processing
Article repository
- To see the effect, open main.html directly in the viewer
- To change the code, execute first
npm i
Install dependencies (using part of es6 code) - Modify the code before executing
npm run dev
Recompile code
Implement a Hello React! The rendering of
Look at the following code:
// js
React.render('hello, React! ',document.getElementById("root"))
// html
<div id="root"></div> // generate code <div id="root">
<span data-reactid="0">hello React! </span> </div>Copy the code
A concrete implementation of the above code
/** * Component class * text type * @param {*} text Text content */functionReactDOMTextComponent(text) {// Stores the current string this._currentElement =""+ text; Component this._rootNodeID = null; component this._rootNodeID = null; } /** * Component class load method, generate DOM structure * @param {number} rootID element id * @return{string} returns the dom * / ReactDOMTextComponent. Prototype. MountComponent =function(rootID) {
this._rootNodeID = rootID;
return (
'<span data-reactid="' + rootID + '" >' + this._currentElement + "</span>"); }; /** * Instantiate a specific Component * @param {*} node ReactElement * @ based on the element typereturn{*} returns a concrete component instance */functionInstantiateReactComponent (node) {/ / text nodeif (typeof node === "string" || typeof node === "number") {
returnnew ReactDOMTextComponent(node); } } const React = { nextReactRootIndex: 1, /** * container receives a React element and a dom node * @param {*} element */ render:function(element, the container) {/ / instantiate the component var componentInstance = instantiateReactComponent (element); / / component complete dom loading var markup = componentInstance. MountComponent (React. NextReactRootIndex++); // Put the loaded DOM into the container $(container).html(markup); $(document).trigger("mountReady"); }};Copy the code
Here the code is divided into three parts:
- 1 React. Render receives a React element as an entry and the dom in the viewer is responsible for calling the render. NextReactRootIndex is the unique identifier for each component
- ReactDOMTextComponent is a Component class definition. The ReactDOMTextComponent is processed for text nodes. The mountComponent method is implemented on the prototype of the ReactDOMTextComponent, which is used to render the component and return the DOM structure of the component. Of course, Component also has update and delete operations, which we’ll explain later.
- 3 instantiateReactComponent used to according to the type of element (in this case, there is only one type string) returns the instance of a component. It’s basically a class factory.
Here we put the logic is divided into several parts, rendering logic is defined by the component internal, React. Render is responsible for scheduling the entire process, in the call instantiateReactComponent generates a corresponding instance objects of type component, Call the mountComponent of the object to return the DOM and write it to the Container node
Virtual dom
The virtual DOM is definitely the core concept behind React. In our code we’ll use the React. CreateElement to create a virtual DOM element.
There are two types of virtual DOM: basic elements that come with the browser, such as divs, and custom elements (text nodes do not count as virtual DOM)
Usage mode of virtual nodes
// Bind the event listener methodfunction sayHello(){
alert('hello! ')
}
var element = React.createElement('div',{id:'jason',onclick:hello},'click me')
React.render(element,document.getElementById("root") // The resulting HTML <div data-reactid="0" id="jason">
<span data-reactid="0.0">click me</span>
</div>
Copy the code
We use React. CreateElement to create a virtual DOM element
@param {*} key ReactElement {*} key ReactElement {*}typeVirtual node type,typeIt could be a string ('div'.'span'), or maybe onefunction.functionIs a property of the virtual node of a custom component * @param {*} props */function ReactElement(type, key, props) {
this.type = type;
this.key = key;
this.props = props;
}
const React = {
nextReactRootIndex: 0,
/**
* @param {*} typeComponent type * @param {*} config element configuration * @param {*} children element */ createElement:function(type, config, children) {
var props = {};
var propName;
config = config || {};
var key = config.key || null;
for (propName in config) {
if(config.hasOwnProperty(propName) && propName ! = ="key") {
props[propName] = config[propName];
}
}
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = Array.isArray(children) ? children : [children];
} else if (childrenLength > 1) {
var childArray = [];
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
return new ReactElement(type, key, props); }, /** * add the previous render method */};Copy the code
The createElement method does some processing on the parameters passed in and returns an instance of the ReactElement virtual element. The key definition makes updating more efficient
With virtual element instance, we need to transform instantiateReactComponent method
/** * Instantiate a specific Component * @param {*} node ReactElement * @ based on the element typereturn{*} returns a concrete component instance */functionInstantiateReactComponent (node) {/ / text nodeif (typeof node === "string" || typeof node === "number") {
returnnew ReactDOMTextComponent(node); } // The browser default nodeif (typeof node === "object" && typeof node.type === "string") {// Notice the use of a new componentreturnnew ReactDOMComponent(node); }}Copy the code
We added a judgment when render is not text but a basic element of the browser. We use another component to handle what it should return when it renders. Here is the factory method instantiateReactComponent benefits, no matter what the type of node, can be responsible for producing a rendering of the component instance. Render doesn’t need to be modified at all, just make a corresponding Component type (in this case, ReactDOMComponent).
Concrete implementation of the ReactDOMComponent
/** * Component class * react Base tag type, similar to HTML ()'div'.'span'* @param {*} element base element */functionReactDOMComponent(Element) {// Save the currentElement object reference this._currentElement = element; this._rootNodeID = null; } / * * * * @ param component class loading method {*} rootID id * @ param {string} returns the dom * / ReactDOMComponent. Prototype. MountComponent =function(rootID) { this._rootNodeID = rootID; var props = this._currentElement.props; // Outer tag var tagOpen ="<" + this._currentElement.type;
var tagClose = "< /" + this._currentElement.type + ">";
// 加上reactid标识
tagOpen += " data-reactid="+ this._rootNodeID; // Concatenate tag attributesfor (var propKey inProps) {// The property is a bound eventif (/^on[A-Za-z]/.test(propKey)) {
var eventType = propKey.replace("on".""); // Add event delegate $(document).delegate('[data-reactid="' + this._rootNodeID + '"],
eventType + "."+ this._rootNodeID, props[propKey] ); } // Children and event properties on props are not handledif( props[propKey] && propKey ! ="children" &&
!/^on[A-Za-z]/.test(propKey)
) {
tagOpen += "" + propKey + "="+ props[propKey]; }} // Render child node dom var content =""; var children = props.children || []; var childrenInstances = []; // Save the child component instance var that = this; children.forEach((child, key) => { var childComponentInstance = instantiateReactComponent(child); // Add the tag childComponentInstance._mountIndex = key; childrenInstances.push(childComponentInstance); var curRootId = that._rootNodeID +"."+ key; / / get the child nodes of rendering contents var childMarkup = childComponentInstance. MountComponent (curRootId); // concatenate content +=""+ childMarkup; }); // Save the Component instance this._renderedChildren = childrenInstances; // Spell out the entire HTML contentreturn tagOpen + ">" + content + tagClose;
};
Copy the code
The rendering logic for the virtual DOM is essentially recursive, and reactElement renders its child nodes recursively. Can see us through instantiateReactComponent blocked the difference of child nodes, only need to use different component class, so that can ensure through mountComponent eventually get the rendered content.
You can pass an argument like {onClick:function(){}} when you pass props, and that will add an event to the current element, propping it into the document. Because React itself is all about writing JS, it’s easy to pass listener functions.
There are a lot of things that aren’t considered here, and I won’t extend them here to keep things simple. React’s event handling is actually quite complex, implementing a standard set of W3C events. This is a lazy way of using jQuery events directly to proxy the document.
Implementation of custom elements As front-end technology has evolved, the basic elements of the browser have outgrown our needs, and if you know anything about Web Components, you know that people have been trying to extend some of their own tags.
React does something similar with the virtual DOM. Remember that Node. type is a simple string. What if it’s a class? If the class happens to have its own life cycle management, it will be very extensible.
Use custom elements in React
var CompositeComponent = React.createClass({
getInitialState: function() {
return {
count: 0
};
},
componentWillMount: function() {
console.log("Declaration cycle:" + "componentWillMount");
},
componentDidMount: function() {
console.log("Declaration cycle:" + "componentDidMount");
},
onChange: function(e) {
var count = ++this.state.count;
this.setState({
count: count
});
},
render: function() {
const count = this.state.count;
var h3 = React.createElement(
"h3",
{ onclick: this.onChange.bind(this), class: "h3" },
`click me ${count}`); var children = [h3];return React.createElement("div", null, children); }}); var CompositeElement = React.createElement(CompositeComponent); var root = document.getElementById("container");
React.render(CompositeElement, root);
Copy the code
React.createElement no longer accepts a string, but a class. React. CreateClass generates a custom tag class with a basic lifecycle:
- GetInitialState Gets the original property value this.state
- ComponentWillmount is called when the component is ready to render
- ComponentDidMount is called after the component is rendered
The realization of the React. CreateClass
/** * Superclasses for all custom components * @functionRender all custom components have this method */function ReactClass() {}
ReactClass.prototype.render = function() {}; / * * * update * @ param {*} newState because new state * / ReactClass prototype. SetState =function(newState because) {/ / get ReactCompositeComponent examples of this. _reactInternalInstance. ReceiveComponent (null, newState because); }; Const React = {nextReactRootIndex: 0, /** * create ReactClass * @param {*} spec passed object */ createClass:function(spec) {
var Constructor = function(props) {
this.props = props;
this.state = this.getInitialState ? this.getInitialState() : null;
};
Constructor.prototype = new ReactClass();
Constructor.prototype.constructor = Constructor;
Object.assign(Constructor.prototype, spec);
returnConstructor; }, /** * createElement method */ ** * render method */;Copy the code
CreateClass generates a subclass that extends from ReactClass, calling this.getInitialState in the constructor to get the original state.
For the sake of demonstration, our side of the ReactClass is quite simple, in fact, the original code to deal with a lot of things, such as mixin class combination inheritance support, such as componentDidMount can be defined multiple times, need to merge calls, etc., interested in looking at the source bar, It is not the main purpose of this article, so I will not go into details here.
Looking at our two types above, it’s time to provide a Component class for custom elements as well, where we instantiate the ReactClass and manage the lifecycle and parent-child component dependencies.
The first reconstruction instantiateReactComponent
/** * Instantiate a specific Component * @param {*} node ReactElement * @ based on the element typereturn{*} returns a concrete component instance */functionInstantiateReactComponent (node) {/ / text nodeif (typeof node === "string" || typeof node === "number") {
returnnew ReactDOMTextComponent(node); } // The browser default nodeif (typeof node === "object" && typeof node.type === "string") {// Notice the use of a new componentreturnnew ReactDOMComponent(node); } // A custom element nodeif (typeof node === "object" && typeof node.type === "function") {// Notice the use of a new Component for custom elementsreturnnew ReactCompositeComponent(node); }}Copy the code
Here we add a judgment to handle components of custom types
The ReactCompositeComponent is implemented as follows
/** * Component class * composite component type * @param {*} Element */functionReactCompositeComponent(Element) {this._currentelement = Element; This. _rootNodeID = null; This. _instance = null; } /** * Component class loading method * @param {*} rootID element id * @param {string} returns dom */ ReactCompositeComponent.prototype.mountComponent =function(rootID) { this._rootNodeID = rootID; Var publicProps = this._currentelement. Props; // ReactClass var ReactClass = this._currentelement. Type; var inst = new ReactClass(publicProps); this._instance = inst; // Keep references to the current Component inst._reactInternalInstance = this;if(inst.ponentWillmount) {// Lifecycle inst.ponentWillmount (); // The original reactJS component calls setState, which does not rerender. Var renderedElement = this._instance.render(); var renderedElement = this._instance.render(); var renderedComponentInstance = instantiateReactComponent(renderedElement); this._renderedComponent = renderedComponentInstance; / / save it for later use var renderedMarkup = renderedComponentInstance. MountComponent (enclosing _rootNodeID); $(document).on("mountReady".function() {
inst.componentDidMount && inst.componentDidMount();
});
return renderedMarkup;
};
Copy the code
The custom element itself is not responsible for the concrete content, it is more responsible for the life cycle. The content is rendered by the virtual nodes returned by its Render method.
It’s essentially a recursive process of rendering content. And because of this recursive nature, the parent component’s componentWillMount must be called before the child component’s componentWillMount, and the parent component’s componentDidMount must be called after the child component. Because the mountReady event must be listened to first by the child component.
Note that the custom element does not process the child node passed in by createElement, it only processes the node returned by render as its child node. But we can use this.props. Children to get the children passed in to render, and we can handle it ourselves. It’s a bit like the Shadow DOM in Web Components.
The initial rendering process is as follows:
Implement a simple update mechanism
In React, we call the setState method when we need to update. So the update in this article is based on setState implementation. Look at the following invocation:
/** * CompositeComponent */ var CompositeComponent = React. CreateClass ({getInitialState:function() {
return {
count: 0
};
},
componentWillMount: function() {
console.log("Declaration cycle:" + "componentWillMount");
},
componentDidMount: function() {
console.log("Declaration cycle:" + "componentDidMount");
},
onChange: function(e) {
var count = ++this.state.count;
this.setState({
count: count
});
},
render: function() {
const count = this.state.count;
var h3 = React.createElement(
"h3",
{ onclick: this.onChange.bind(this), class: "h3" },
`click me ${count}`); var children = [h3];return React.createElement("div", null, children); }}); var CompositeElement = React.createElement(CompositeComponent); var root = document.getElementById("root"); React.render(CompositeElement, root); // generate HTML <div id="root">
<div data-reactid="0">
<h3 data-reactid="0.0" class="h3">
<span data-reactid="0.0.0">click me 0</span> </h3> </div> </div> // click me count incrementCopy the code
Click on the text and call setState to go through the update process, so let’s review the ReactClass and look at the implementation of setState
/ * * * update * @ param {*} newState because new state * / ReactClass prototype. SetState =function(newState) {// Get the ReactCompositeComponent instance // save the code when loading: this._reactInternalInstance = this this._reactInternalInstance.receiveComponent(null, newState); };Copy the code
As you can see, setState mainly calls the corresponding Component’s receiveComponent to implement the update. All mounts and updates should be managed by the corresponding Component. So just as all Components implement mountComponent to handle the first render, all Component classes should implement receiveComponent to handle their own updates.
ReceiveComponent of the text node
Text node update is relatively simple, get the new text for comparison, different directly replace the whole node
/ component class * * * update * @ param {*} newText * / ReactDOMTextComponent prototype. ReceiveComponent =function(nextText) {
var nextStringText = ""+ nextText; // Compare with the previously saved stringif(nextStringText ! == this._currentElement) { this._currentElement = nextStringText; // Replace the entire node $('[data-reactid="' + this._rootNodeID + '"]).html(this._currentElement); }};Copy the code
ReceiveComponent for custom elements
Let’s start with the receiveComponent implementation of the custom element
/** * Component class update * @param {*} nextElement * @param {*} newState */ ReactCompositeComponent.prototype.receiveComponent =function(nextElement, newState) {// If a new element is accepted, Directly using the latest element enclosing _currentElement = nextElement | | this. _currentElement; var inst = this._instance; Var nextState = object.assign (inst.state, newState); var nextProps = this._currentElement.props; // Update state inst.state = nextState; // Lifecycle methodsif (
inst.shouldComponentUpdate &&
inst.shouldComponentUpdate(nextProps, nextState) === false) {// If the instance's shouldComponentUpdate returnsfalse, you do not need to proceed to the updatereturn; } // Lifecycle methodsif(inst.componentWillUpdate) inst.componentWillUpdate(nextProps, nextState); Element var prevComponentInstance = this._renderedComponent; var prevRenderedElement = prevComponentInstance._currentElement; Var nextRenderedElement = this._instance.render(); // Compare old and new elementsif(_shouldUpdateReactComponent (prevRenderedElement nextRenderedElement)) {/ / two elements for the same, need to be updated, Implement bytes point update prevComponentInstance. ReceiveComponent (nextRenderedElement); // Lifecycle method inst.ponentdiDupDate && inst.ponentdidupDate (); }elseVar thisID = this._rootNodeID; this._renderedComponent = this._instantiateReactComponent( nextRenderedElement ); var nextMarkup = _renderedComponent.mountComponent(thisID); // Replace the entire node $('[data-reactid="' + this._rootNodeID + '"]).replaceWith(nextMarkup); }}; /** * Compare two elements to determine whether the * @param {*} preElement old element * @param {*} nextElement new element * @return {boolean}
*/
function _shouldUpdateReactComponent(prevElement, nextElement) {
if(prevElement ! = null && nextElement ! = null) { var prevType = typeof prevElement; var nextType = typeof nextElement;if (prevType === "string" || prevType === "number") {// Text nodes compare whether they are of the same typereturn nextType === "string" || nextType === "number";
} else{/ / passtypeAnd key to check whether the node is of the same type and the same nodereturn (
nextType === "object"&& prevElement.type === nextElement.type && prevElement.key === nextElement.key ); }}return false;
}
Copy the code
The general flow of the above code is as follows:
- Merger of the state
- Update the state
- ShouldComponentUpdate is called if the business code implements the lifecycle method, and stops executing if the return value is false
- Then there’s the life cycle method componentWillUpdate
- We then call the Render method with the new state to get the new element and compare it with the old one
- To update, call the component class receiveComponent, which is the receiveComponent. Of course, there are also cases where the two generated elements are so different that they are not of the same type, so you can simply generate a new code and re-render it once
_shouldUpdateReactComponent is a global approach, this is a kind of optimization of the React mechanism. Used to decide whether to replace all of them directly or to use minor changes. When two render out the child node key is different, directly all re-render again, replacement is good. Otherwise, we have to do a recursive update to keep the update mechanism minimal so that there are not too many flickers.
This is essentially a recursive call to receiveComponent.
ReceiveComponent for the base element
The updates to the base elements are twofold
- Property updates, including handling of special properties such as events
- Updates of child nodes
The updating of child nodes is complicated and crucial to improve efficiency, so the following problems need to be dealt with:
- Diff – Compare the new child tree with the old child tree to find the differences between them.
- Patch – Update once all the differences are found out.
Here is the basic structure of the base element update
/ component class * * * update * @ param {*} nextElement * / ReactDOMComponent prototype. ReceiveComponent =function(nextElement) { var lastProps = this._currentElement.props; var nextProps = nextElement.props; this._currentElement = nextElement; // Handle the current node's property this._updatedomProperties (lastProps, nextProps); / / processing child nodes of the current node changes this. _updateDOMChildren (nextElement. Props. Children); };Copy the code
Let's take a look at how the update property changes:
/ update properties * * * * @ param {*} lastProps * @ param {*} nextProps * / ReactDOMComponent prototype. _updateDOMProperties =function(lastProps, nextProps) {// If the old attribute is not in the set of new attributes, remove the attribute var propKey;for (propKey in lastProps) {
if( nextProps.hasOwnProperty(propKey) || ! LastProps. HasOwnProperty (propKey)) {// There is a new property, and there is no longer a prototype for the old propertycontinue;
}
if (/^on[A-Za-z]/.test(propKey)) {
var eventType = propKey.replace("on".""); $(document).undelegate()'[data-reactid="' + this._rootNodeID + '"],
eventType,
lastProps[propKey]
);
continue; } // Delete the unneeded attribute $('[data-reactid="' + this._rootNodeID + '"]).removeAttr(propKey); } // For new events, write to domfor (propKey in nextProps) {
if (/^on[A-Za-z]/.test(propKey)) {
var eventType = propKey.replace("on".""); // Remove the old event binding lastProps[propKey] && $(document).undelegate('[data-reactid="' + this._rootNodeID + '"], eventType, lastProps[propKey] ); // Add an event delegate for the current node with _rootNodeID as namespace $(document).delegate('[data-reactid="' + this._rootNodeID + '"],
eventType + "." + this._rootNodeID,
nextProps[propKey]
);
continue;
}
if (propKey == "children") continue; // Add a new attribute, override the same attribute $('[data-reactid="' + this._rootNodeID + '"]).prop( propKey, nextProps[propKey] ); }};Copy the code
Attribute change is not particularly complex, mainly to find the old and unused attributes directly removed, new attribute assignment, and pay attention to the special event attribute to make special processing.
Child node updates are also the most complicated part:
Var updateDepth = 0; Var diffQueue = []; var diffQueue = []; ReactDOMComponent.prototype._updateDOMChildren =function( nextChildrenElements ) { updateDepth++; // _diff is used to recursively find differences, assemble difference objects, and add them to the update queue diffQueue. this._diff(diffQueue, nextChildrenElements); updateDepth--;if(updateDepth == 0) {// Call patch when needed and perform the dom operation this._patch(diffQueue); diffQueue = []; }};Copy the code
As we said earlier, updating child nodes consists of two parts, one is to recursively analyze the differences and add them to the queue. Then call _patch when appropriate to apply the difference to the DOM. So what’s the right time, and what’s updateDepth for? Note that _diff also recursively calls the receiveComponent of the child node, so if a child node is also a common browser node, the _updateDOMChildren step is also used. Therefore, updateDepth is used to record the recursion process. When updateDepth is 0 after recursion, it means that the whole difference analysis has been completed and patch can be used to process the difference queue.
The diff implementation
Var UPDATE_TYPES = {MOVE_EXISTING: 1, REMOVE_NODE: 2, INSERT_MARKUP: 3}; @param {object} prevChildren; @param {Array} NextChildrenElements The element array of the newly passed child node * @return{object} returns a mapping */function generateComponentChildren(prevChildren, nextChildrenElements) {
var nextChildren = {};
nextChildrenElements = nextChildrenElements || [];
$.each(nextChildrenElements, function(index, element) { var name = element.key ? element.key : index; var prevChild = prevChildren && prevChildren[name]; var prevElement = prevChild && prevChild._currentElement; var nextElement = element; / / call _shouldUpdateReactComponent judgment is updatedif(_shouldUpdateReactComponent(prevElement, NextElement)) {/ / update direct recursive calls to the child node's receiveComponent prevChild. ReceiveComponent (nextElement); // Then continue with the old Component nextChildren[name] = prevChild; }else{/ / for not old, then to a new one, to generate a component var nextChildInstance = instantiateReactComponent (nextElement, null); // Use the new Component nextChildren[name] = nextChildInstance; }});returnnextChildren; } /** * convert arrays to maps * @param {Array} componentChildren * @return{object} returns a mapping */function flattenChildren(componentChildren) {
var child;
var name;
var childrenMap = {};
for (var i = 0; i < componentChildren.length; i++) {
child = componentChildren[i];
name =
child && child._currentelement && child._currentelement.key
? child._currentelement.key
: i.toString(36);
childrenMap[name] = child;
}
returnchildrenMap; } /** * _diff is used to recursively find differences, assemble difference objects, and add them to the update queue diffQueue. * @param {*} diffQueue * @param {*} nextChildrenElements */ ReactDOMComponent.prototype._diff =function(diffQueue, nextChildrenElements) { var self = this; Renderedchildren is an array of renderedChildren. Renderedchildren is an array of renderedChildren. Renderedchildren is an array of renderedChildren. 例 句 : We formed a map var prevChildren = a flattener; // Generate a new set of component objects for child nodes. Will reuse old component object var nextChildren = generateComponentChildren (prevChildren nextChildrenElements); // reassign _renderedChildren to use the latest. self._renderedChildren = []; $.each(nextChildren,function(key, instance) { self._renderedChildren.push(instance); }); Var lastIndex = 0; Var nextIndex = 0; var nextIndex = 0; // Add the difference node to the queue by comparing the differences between the two setsfor (name in nextChildren) {
if(! nextChildren.hasOwnProperty(name)) {continue; } var prevChild = prevChildren && prevChildren[name]; var nextChild = nextChildren[name]; // The same component is used, so we need to move itif(prevChild === nextChild) {// Add difference object, type: **/ prevChild._mountIndex < lastIndex &&diffQueue.push ({parentId: self._rootNodeID, parentNode: $("[data-reactid=" + self._rootNodeID + "]"),
type: UPDATE_TYPES.MOVE_EXISTING, fromIndex: prevChild._mountIndex, toIndex: nextIndex }); /** / lastIndex = math.max (prevchild._mountIndex, lastIndex); }else{// If the element is not the same, it is a new node. // If the old element exists, it is the same component. We need to remove the corresponding old element.ifPush ({parentId: self._rootNodeID, parentNode: $(prevChild) {// Add diffqueue. push({parentId: self._rootNodeID, parentNode: $("[data-reactid=" + self._rootNodeID + "]"),
type: UPDATE_TYPES.REMOVE_NODE, fromIndex: prevChild._mountIndex, toIndex: null }); // If it has been rendered before, remember to remove all previous event listeners and clear them from the namespaceif (prevChild._rootNodeID) {
$(document).undelegate("."+ prevChild._rootNodeID); **/ prevchild._mountIndex = math.max (prevchild._mountIndex, lastIndex); INSERT_MARKUP diffqueue.push ({parentId: self._rootNodeid, parentNode: $())"[data-reactid=" + self._rootNodeID + "]"),
type: UPDATE_TYPES.INSERT_MARKUP,
fromIndex: null,
toIndex: nextIndex,
markup: nextChild.mountComponent(self._rootNodeID + "."+ name) // dom content of the new node}); } // Update mount index nextchild. _mountIndex = nextIndex; nextIndex++; } // Delete all nodes that are present in the old node and not present in the new nodefor (name in prevChildren) {
if( prevChildren.hasOwnProperty(name) && ! (nextChildren && nextChildren. HasOwnProperty (name))) {/ / add different object, type: REMOVE_NODE diffQueue. Push ({parentId: self._rootNodeID, parentNode: $("[data-reactid=" + self._rootNodeID + "]"),
type: UPDATE_TYPES.REMOVE_NODE, fromIndex: prevChildren[name]._mountIndex, toIndex: null }); // If it has been rendered before, remember to remove all previous event listeners firstif (prevChildren[name]._rootNodeID) {
$(document).undelegate("."+ prevChildren[name]._rootNodeID); }}}};Copy the code
Note that the flattener children turned the array collection into a map of objects identified by the element’s key, but for text text or elements that pass no key, the flattener was identified by an index. Using these identifiers, we can tell if two Components are the same from a type perspective.
GenerateComponentChildren will try to reuse the previous component, also is the pit, when found can reuse component (that is, the key agreement), is also used before, Just call its corresponding update method receiveComponent, which will recursively fetch the child node’s difference object and place it on the queue. If found that can’t reuse it’s new nodes, we need instantiateReactComponent regenerate a new component.
LastIndex, which represents the largest location of the old collection node that was last accessed. And we added a judgment that only those whose _mountIndex is less than this lastIndex need to be added to the difference queue. Given this judgment, example 2 above does not need to move. And the program will work fine, and in fact most of the cases are 2.
This is a sequential optimization where lastIndex is constantly updated and represents the elements of the old, right-most set currently accessed. Let’s assume that the last element was A, and the addition updates the lastIndex. If we have A new element, B, that’s bigger than lastIndex means that the current element is later than the last A in the old set. So if this element is not added to the difference queue, it will not affect other people, and it will not affect the path insertion node later. As we know from Patch, new sets are inserted from scratch in order, and only when the new elements are smaller than lastIndex need to be changed. In fact, if you look at the above example carefully, you can understand this method of optimization. Check the React Diff policy
The realization of the _patch
/**
*
* @param {*} parentNode
* @param {*} childNode
* @param {*} index
*/ function insertChildAt(parentNode, childNode, index) {
var beforeChild = parentNode.children().get(index);
beforeChild
? childNode.insertBefore(beforeChild)
: childNode.appendTo(parentNode);
}
/**
*
* @param {*} diffQueue
*/
ReactDOMComponent.prototype._patch = function(diffQueue) {
var update;
var initialChildren = {};
var deleteChildren = [];
for (var i = 0; i < updates.length; i++) {
update = updates[i];
if( update.type === UPDATE_TYPES.MOVE_EXISTING || update.type === UPDATE_TYPES.REMOVE_NODE ) { var updatedIndex = update.fromIndex; var updatedChild = $(update.parentNode.children().get(updatedIndex)); var parentID = update.parentID; / / all of the nodes need to be updated, convenient behind use initialChildren [parentID] = initialChildren [parentID] | | []; InitialChildren [parentID][updatedIndex] = updatedChild; DeleteChildren. Push (updatedChild); deleteChildren. Push (updatedChild); $. Each (deleteChildren,function(index, child) { $(child).remove(); }); // Go through it again, this time for the new nodes, and also for the modified nodesfor (var k = 0; k < updates.length; k++) {
update = updates[k];
switch (update.type) {
case UPDATE_TYPES.INSERT_MARKUP:
insertChildAt(update.parentNode, $(update.markup), update.toIndex);
break;
case UPDATE_TYPES.MOVE_EXISTING:
insertChildAt(
update.parentNode,
initialChildren[update.parentID][update.fromIndex],
update.toIndex
);
break;
caseUpdate_types. REMOVE_NODE: // There is nothing to do because it has already been removedbreak; }}};Copy the code
_patch mainly traverses the difference queue one by one, traversing it twice, deleting all nodes that need to be changed in the first time, and then inserting new nodes and modified nodes in the second time. Why can I just insert them one by one here? The reason is that when we add the difference nodes to the difference queue in the diff stage, it is in order itself, that is to say, the order of the new nodes (including move and insert) in the queue is the final DOM order, so we can directly insert nodes one by one according to the index.
This completes the entire update mechanism. Here's a quick review of React's difference algorithm:
The first is that all components implement receiveComponent to take care of their updates, and the browser’s default element updates are the most complex, often called the Diff algorithm.
React with a global _shouldUpdateReactComponent used to judging according to the key element is to update or to render, this is the first differences of judgment. For custom elements, for example, this can be particularly efficient with identification.
Each type of element handles its own updates:
-
Updates to custom elements are made to render nodes, which are handled by the render node’s component.
-
The update of the text node is simple, updating the copy directly.
-
The basic browser elements are updated in two parts:
- First, update the attributes, compare the differences between the attributes before and after, local update. And handles special properties, such as event bindings.
- Then the child node updates, child node update mainly find out differences between objects, looking for differences in object will also use the above _shouldUpdateReactComponent, if can be directly update will recursive calls to the child node updates, such differences also recursive search object, We’ll also use lastIndex as an optimization to keep some nodes in position and then manipulate DOM elements based on the difference object (position change, delete,
end
It’s just a toy, but it implements the core features of React: virtual nodes, differential algorithms, and one-way updates. There are a lot of good things about React that are not implemented, such as thread pool management for object generation, batch update mechanism, event optimization, server-side rendering, immutable data, etc. I don’t want to go into all of this because of space.
React as a solution, the idea of virtual nodes is a bit novel, but I’m still not comfortable with the awkward way it’s written. React requires a whole set of development methods, and its core function is actually just a differential algorithm, which has already been implemented in related libraries.
Related information:
- The React diff strategy
- The original address
- React React Backend: Enterprise-level middle backend projects