[React in-depth] In-depth analysis of virtual DOM rendering principles and characteristics

image

Introduction

ReactVirtual ofDOMAndDiffThe algorithm isReactThis part of the source code is also very complex. Understanding the principle of this part of knowledge will help you to master it more deeply.ReactIt is very necessary.

Originally wanted to virtualDOMAndDiffThe algorithm is put into an article and the virtual is finishedDOMIt is found that the article is already very long, so this article only analyzes the virtualDOM.

This article starts from the source code, analyzes the virtualDOMThe core rendering principle of (first rendering), andReactThe performance optimization point for it.

tell the truthReactThe source code is really hard to read. If this article helps you, please give me a compliment and support.

Common Problems in Development

  • Why must it be quotedReact
  • CustomReactWhy do components have to be capitalized
  • ReactHow to preventXSS
  • ReactTheDiffAlgorithms and othersDiffWhat is the difference between algorithms
  • keyInReactThe role of
  • How to Write High PerformanceReactComponent

If you still have questions about the above questions, you are rightReactVirtual ofDOMas well asDiffThe principle of algorithm implementation is still lacking, so please read this article carefully.

First, let’s look at what is virtualDOM:

Virtual DOM

image

In the originalJavaScriptIn the process, we directly toDOMTo create and change, andDOMElements communicate with our applications through the events we monitor.

AndReactWill first convert your code into aJavaScriptObject, and then thisJavaScriptObjects are then converted to realityDOM. This ..JavaScriptObjects are so-called virtualDOM.

For example, the following paragraphhtmlCode:

<div class="title">
<span>Hello ConardLi</span>
<ul>
< li > apple < /li >
< li > oranges < /li >
</ul>
</div>

InReactMay be stored asJSCode:

const VitrualDom = {
type: 'div',
props: { class: 'title' },
children: [
{
type: 'span',
children: 'Hello ConardLi'
},
{
type: 'ul',
children: [
{type: 'li', children:' apple'},
{type: 'li', children:' orange'}
]
}
]
}

When we need to create or update elements,ReactFirst, let thisVitrualDomObject is created and changed, and then theVitrualDomObject Renders RealDOM;

When we need to be rightDOMWhen monitoring an event, first monitor theVitrualDomTo monitor events,VitrualDomWill represent the originalDOMEvent to respond.

Why Use Virtual DOM

ReactWhyVitrualDomWhat about this plan?

Improve development efficiency

UseJavaScriptWhen writing applications, our focus is on how to updateDOM.

UseReact, you just need to tellReactWhat state do you want the view to be in?ReactPassVitrualDomguaranteeDOMMatches this state. You don’t have to complete attribute operations, event handling,DOMUpdate,ReactI will do all this for you.

This makes us pay more attention to our business logic than to it.DOMOperation, this can greatly improve our development efficiency.

About Improving Performance

Many articles saidVitrualDomCan improve performance, this statement is actually very one-sided.

direct operationDOMIt is very performance-intensive, and there is no doubt about it. butReactUseVitrualDomIt is also an unavoidable operation.DOMYes.

If this is the first rendering,VitrualDomIt does not have any advantages, even it needs more calculations and consumes more memory.

VitrualDomThe advantage lies inReactTheDiffAlgorithms and batch processing strategies,ReactBefore the page is updated, how to update and render is calculated in advance.DOM. In fact, we are directly operating this calculation process.DOMHowever, it will definitely take a lot of energy and time, and often what we do is not as good as what we do ourselves.ReactAll right So, in this processReactHelped us “improve performance.”

So, I prefer to say,VitrualDomIt helps us to improve the development efficiency. It helps us to calculate how to update more efficiently when rendering repeatedly, rather than comparing it with other methods.DOMThe operation is faster.

If you have any different opinions on this part of the analysis, please click on the comment area.

Cross browser compatibility

image

ReactBased onVitrualDomI have implemented a set of my own event mechanism. I have simulated the process of event bubbling and capturing. I have used event proxy, batch update and other methods to smooth out the issue of event compatibility of various browsers.

Cross-platform compatibility

image

VitrualDomForReactIt brings the ability of cross-platform rendering. In order toReact NativeFor example.ReactAccording toVitrualDomDraw the corresponding platformuiLayer, just different platform painting posture is different.

Implementation principle of virtual DOM

If you don’t want to read the complicated source code, or don’t have enough time now, you can skip this chapter and go straight to it.Summary of virtual DOM principle

image

In the above figure we continue to expand, according to the flow in the figure, we analyze the virtual in turnDOMThe realization principle of.

JSX and createElement

We are implementing aReactThere are two encoding methods for components, the first is to useJSXWriting:

class Hello extends Component {
 render() {
 return <div>Hello ConardLi</div>;
 }
 }

The second is direct useReact.createElementWriting:

class Hello extends Component {
render() {
return React.createElement('div', null, `Hello ConardLi`);
}
}

In fact, the above two writing methods are equivalent.JSXJust forReact.createElement(component, props, ...children)The syntax sugar provided by the method. That is to say, allJSXThe code will eventually be converted toReact.createElement(...),BabelHelped us to complete the conversion process.

As followsJSX

<div>
<img src="avatar.png" className="profile" />
<Hello />
</div>;

Will beBabelConvert to

React.createElement("div", null, React.createElement("img", {
src: "avatar.png",
className: "profile"
}), React.createElement(Hello, null));

Attention,babelIt is determined at compile timeJSXThe first letter of a component in is considered native when the first letter is lowercase.DOMLabels,createElementThe first variable of is compiled into a string; When the initial letter is uppercase, it is considered a custom component.createElementThe first variable of is compiled into an object;

In addition, due toJSXIn advance to beBabelCompile, soJSXYou cannot dynamically select a type at runtime, such as the following code:

function Story(props) {
// Wrong!  JSX type can't be an expression.
return <components[props.storyType] story={props.story} />;
}

Need to become the following writing:

function Story(props) {
// Correct!  JSX type can be a capitalized variable.
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}

So, useJSXYou need to installBabelplug-inbabel-plugin-transform-react-jsx

{
 "plugins": [
 ["transform-react-jsx", {
 "pragma": "React.createElement"
 }]
 ]
 }

Create virtual DOM

Let’s take a look at the virtualDOMThe real appearance, the followingJSXThe code is printed on the console:

<div className="title">
 <span>Hello ConardLi</span>
 <ul>
 < li > apple < /li >
 < li > oranges < /li >
 </ul>
 </div>

image

This structure is very similar to the one we described above, soReactHow to convert our code into this structure, let’s take a lookcreateElementThe concrete realization of the function (the source code in this article has been simplified).

image

createElementThe operation inside the function is very simple and willpropsAnd returns aReactElementObject, let’s analyze one by one:

(1). Handling props:

image

  • 1. The special propertiesrefkeyFromconfigTake out and assign a value from
  • 2. The special propertiesselfsourceFromconfigTake out and assign a value from
  • 3. Take out other attributes except special attributes and assign them toprops

Later articles will describe in detail the function of these special attributes.

(2) acquiring child elements

image

  • 1. Get the number of child elements-all parameters after the second parameter
  • 2. If there is only one child element, assign it toprops.children
  • 3. If there are multiple sub-elements, fill the sub-elements into an array and assign it toprops.children

(3). Handling Default props

image

  • The static properties of the componentdefaultPropsDefined defaultpropsMake an assignment

ReactElement

ReactElementCombines several attributes passed in and returns.

  • type: The type of element, which can be a native html type (string) or a custom component (function orclass)
  • key: Unique identifier of the component used toDiffThe algorithm will be described in detail below.
  • ref: used to access nativedomNode
  • props: passed into the component’sprops
  • owner: currently under constructionComponentBelonging toComponent

$$typeof: an attribute that we seldom see, it is assigned toREACT_ELEMENT_TYPE

var REACT_ELEMENT_TYPE =
 (typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) ||
 0xeac7;

Obviously,$$typeofIs aSymbolA variable of type that preventsXSS.

If your server has a vulnerability, allow users to store arbitraryJSONObject, and client code requires a string, which may become a problem:

// JSON
 let expectedTextButGotJSON = {
 type: 'div',
 props: {
 dangerouslySetInnerHTML: {
 __html: '/* put your exploit here */'
 },
 },
 };
 let message = { text: expectedTextButGotJSON };
 <p>
 {message.text}
 </p>

JSONCannot store inSymbolVariable of type.

ReactElement.isValidElementFunction is used to judge aReactWhether the component is valid or not, the following is its concrete implementation.

ReactElement.isValidElement = function (object) {
 return typeof object === 'object' && object !  == null && object.$$typeof === REACT_ELEMENT_TYPE;
 };

VisibleReactWhen rendering will not$$typeofIdentifiers, as well as components that the rule check fails, are filtered out.

When your environment does not support itSymbolAt that time,$$typeofAssigned to0xeac7As to why,ReactThe developer gave the answer:

0xeac7It looks a bit likeReact.

selfsourceOnly in a non-production environment will it be added to the object.

  • selfSpecifies which component instance is currently located.
  • _sourceSpecifies the file from which the debugging code comes (fileName) and the number of lines of code (lineNumber)。

The virtual DOM is converted to the real DOM.

Above we analyzed the code conversion to virtualDOMLet’s take a look at the process ofReactHow to make virtualDOMConvert to realityDOM.

This part of the logic is more complex, let’s use the flow chart to sort out the whole process, the whole process can be roughly divided into four steps:

image

Process 1: Initial Parameter Processing

In the preparation of ourReactComponent, we need to call theReactDOM.render(element, container[, callback])Render the component.

renderThe function was actually called inside_renderSubtreeIntoContainer, let’s look at its concrete implementation:

render: function (nextElement, container, callback) {
 return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);
 },

image

  • 1. Use the current componentTopLevelWrapperCarry out a package

TopLevelWrapperIt is only an empty shell. It provides a component that you need to mount.rootIDProperty, and in therenderFunction to return the component.

TopLevelWrapper.prototype.render = function () {
 return this.props.child;
 };

ReactDOM.renderThe first parameter of a function can be nativeDOMIt can also beReactComponents, wrap one layerTopLevelWrapperThey can be processed in a unified way in subsequent rendering, regardless of whether they are native or not.

  • (2) judging whether elements have been rendered under the root knot point, and if so, judging whether updating or unloading operations are performed
  • 3. HandlingshouldReuseMarkupVariable that indicates whether the element needs to be relabeled.
  • 4. Call to pass in the above processed parameters_renderNewRootComponentCalled when rendering is completecallback.

In_renderNewRootComponentCall ininstantiateReactComponentClassify and package the components we pass in:

image

Depending on the type of component,ReactAccording to the original components, the following four types of components were created to classify and render the components:

  • ReactDOMEmptyComponent: empty component
  • ReactDOMTextComponent: text
  • ReactDOMComponent: primaryDOM
  • ReactCompositeComponent: customReactComponent

They all have the following three methods:

  • construct: Used to receiveReactElementInitialize.
  • mountComponent: used to generateReactElementCorresponding truthDOMOrDOMLazyTree.
  • unmountComponent: uninstallDOMNode, unbind event.

Specifically, how to render is analyzed in process 3.

Procedure 2: Batch Processing, Transaction Invocation

In_renderNewRootComponentUsed inReactUpdates.batchedUpdatescallbatchedMountComponentIntoNodeBatch processing.

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

InbatchedMountComponentIntoNodeIn, usetransaction.performcallmountComponentIntoNodeLet it call based on transaction mechanism.

transaction.perform(mountComponentIntoNode, null, componentInstance, container, transaction, shouldReuseMarkup, context);

Regarding batch processing transactions, the analysis before meSetState execution mechanismThere are more introductions in.

Procedure 3: Generate html

InmountComponentIntoNodeCall in functionReactReconciler.mountComponentGenerate primordialDOMNode.

mountComponentThe inside actually calls the four objects generated by procedure 1mountComponentMethods. First of all, let’s take a lookReactDOMComponent

image

  • 1. SpecialDOMLabels,propsFor processing.
  • 2. Create according to label typeDOMNode.
  • 3. Call_updateDOMPropertieswillpropsInsert intoDOMNodes,_updateDOMPropertiesIt can also be used forprops DiffThe first parameter is the last renderedpropsThe second parameter is the currentprops, if the first parameter is empty, it is the first time to create.
  • 4. Generate aDOMLazyTreeObject and call the_createInitialChildrenRenders the child node to the top.

Then why not generate one directlyDOMInstead of creating a nodeDOMLazyTree? Let’s take a look first_createInitialChildrenWhat has been done:

image

Judging the current node’sdangerouslySetInnerHTMLProperty, whether the child node is text and other nodes are called separatelyDOMLazyTreeThequeueHTMLqueueTextqueueChild.

image

It can be found that:DOMLazyTreeIs actually a package object,nodeProperty stores the realDOMNodes,childrenhtmltextChildren, html nodes and text nodes are stored respectively.

It provides several methods for inserting children,htmlAs well as text nodes, these insertions are conditional whenenableLazyThe property istrueWhen, these children,htmlAnd the text node will be inserted into theDOMLazyTreeObject, when it isfalseWill be inserted into the realDOMIn the node.

var enableLazy = typeof document !  == 'undefined' &&
typeof document.documentMode === 'number' ||
typeof navigator !  == 'undefined' &&
typeof navigator.userAgent === 'string' &&
/\bEdge\/\d/.test(navigator.userAgent);

Visible:enableLazyIs a variable, the current browser isIEOrEdgeTime istrue.

InIE(8-11)AndEdgeIn the browser, inserting nodes without descendants one by one is much more efficient than inserting a complete serialized node tree.

solazyTreeThe main solution is inIE(8-11)AndEdgeThe efficiency of inserting nodes into browsers will be analyzed in the following process 4: if it is currentlyIEOrEdge, recursive insertion is requiredDOMLazyTreeFor child nodes cached in the, other browsers only need to insert the current node once, because their children have already been rendered without worrying about efficiency.

Let’s take a lookReactCompositeComponentBecause there is so much code, the code of this module will no longer be posted here, and the following steps have been taken inside:

  • To deal withpropscontexEqual variables, call the constructor to create a component instance
  • Judging whether it is a stateless component, processingstate
  • callperformInitialMountLife cycle, processing child nodes, obtainingmarkup.
  • callcomponentDidMountLife cycle

InperformInitialMountFunction, the first callcomponentWillMountLife cycle, due to customReactThe component is not a real DOM, so the child node’s is called again in the functionmountComponent. This is also a recursive process, when all child nodes render, returnmarkupAnd callcomponentDidMount.

Process 4: Rendering html

InmountComponentIntoNodeFunction will be generated in the previous stepmarkupInsertcontainerContainer.

When rendering for the first time,_mountImageIntoNodeWill emptycontainerCalled after the child node ofDOMLazyTree.insertTreeBefore

image

Judge whether it isfragmentNode or<object>Plug-ins:

  • If it is the above two, first callinsertTreeChildrenRender the child node of this node to the current node, and then insert the rendered node into thehtml
  • If it is another node, first insert the node into thehtml, and then callinsertTreeChildrenInsert child node intohtml.
  • If not currentlyIEOrEdgeYou do not need to insert child nodes recursively, you only need to insert the current node once.

image

  • Judge notIEOrbEdgeWhenreturn
  • IfchildrenNot empty, recursiveinsertTreeBeforeInsert
  • Rendering html nodes
  • Render text node

Native DOM event proxy

About virtualizationDOMThe incident mechanism, I have written a special article, interested in canReact Incident Mechanism

Summary of Principle and Characteristics of Virtual DOM

Rendering Process of React Component

  • UseReact.createElementOrJSXwriteReactComponents, virtually allJSXThe code will eventually be converted toReact.createElement(...),BabelHelped us to complete the conversion process.
  • createElementFunction pairkeyAndrefSuch as specialpropsProcessing and obtainingdefaultPropsTo defaultpropsAssigning a value, and processing the incoming child nodes to finally construct aReactElementObject (so-called virtualDOM)。
  • ReactDOM.renderA good virtual will be generated.DOMRender to the specified container, which adopts batch processing, transaction and other mechanisms and optimizes the performance of specific browsers, and finally converts to realityDOM.

Composition of virtual DOM

That is,ReactElementElement object, our components will eventually be rendered into the following structure:

  • type: The type of element, which can be a native html type (string) or a custom component (function orclass)
  • key: Unique identifier of the component used toDiffThe algorithm will be described in detail below.
  • ref: used to access nativedomNode
  • props: passed into the component’sprops,chidrenYespropsAn attribute in that stores the child nodes of the current component, which can be an array (multiple child nodes) or an object (only one child node)
  • owner: currently under constructionComponentBelonging toComponent
  • self: (non-production environment) specifies which component instance is currently located
  • _source: (non-production environment) specifies the file from which the debug code comes (fileName) and the number of lines of code (lineNumber)

Prevent XSS

ReactElementThere is another object$$typeof' attribute, which is a variable of type' Symbol'. for ('reach. element') `, when the environment does not support' Symbol', ` $$typeof'Assigned to0xeac7.

This variable preventsXSS. If your server has a vulnerability, allow users to store arbitraryJSONObject, while client code requires a string, which may pose risks to your application.JSONCannot store inSymbolVariable of type, whileReactWhen rendering will not$$typeofIdentified components are filtered out.

Batch processing and transactions

ReactIn rendering virtualDOMBatch processing and transaction mechanism are applied to improve rendering performance.

As for batch processing and transaction mechanism, in my previous articles[React Deeper] setState’s Execution MechanismIt is described in detail in.

Targeted performance optimization

InIE(8-11)AndEdgeIn the browser, inserting nodes without descendants one by one is much more efficient than inserting a complete serialized node tree.

ReactvialazyTreeInIE(8-11)AndEdgeIn the browser, the node is rendered sequentially by a single node, while in other browsers, the entire largeDOMThe structure is constructed and then inserted into the container as a whole.

Moreover, when the nodes are individually rendered,ReactAlso consideredfragmentSuch as special nodes, these nodes will not be inserted into rendering one by one.

Virtual DOM event mechanism

ReactI have implemented a set of event mechanisms myself, which bind all in the virtualDOMEvents on map to realDOMEvent and proxy all events todocumentIn fact, I simulated the process of bubbling and capturing events, and carried out unified event distribution.

ReactHe constructed the composite event object himself.SyntheticEvent, which is a cross-browser native event wrapper. It has the same interface as browser native events, includingstopPropagation()AndpreventDefault()Wait, they work the same way in all browsers. This smoothes out the issue of event compatibility for each browser.

The above analysis only virtualDOMThe principle and process of the first rendering, of course, this does not include virtualDOMcarry throughDiffWe will discuss the process in detail in the next article.

As for the questions raised at the beginning, we will give a unified answer in the next article.

Recommended reading

End

The version in the source code of this article isReactVersion 15, relative16There will be some discrepancies in the version, regarding16Changes to the version will be analyzed separately in subsequent articles.

If there are any mistakes in the article, please point them out in the comment area, or if you have any good suggestions on the composition and reading experience of the article, please point them out in the comment area and thank you for reading.

Want to read more high-quality articles, download source files of mind maps in articles, and read articlesdemoSource code, can pay attention to meGithub blogYour star✨, praise and attention are the driving force of my continuous creation!

It is recommended to pay close attention to my WeChat public number “code Secret Garden” and push high-quality articles every day so that we can communicate and grow together.

图片描述