takeaway

React virtual DOM and Diff algorithm are very important core features of React. The source code of React is also very complex. Understanding the principles of this knowledge is essential for a deeper understanding of React.

I originally wanted to put the virtual DOM and Diff algorithm into one article, but after writing the virtual DOM, I found that the article was already very long, so this article only analyzes the virtual DOM.

This article analyzes the core rendering principles of the virtual DOM (first rendering) and the performance optimization points made by React.

Honestly, the React source code is really hard to read at 😅. If this article helps you, please give it a thumbs up at 👍.

Frequently asked questions in development

  • Why it must be referencedReact
  • The custom ofReactWhy must components be capitalized
  • ReactHow to preventXSS
  • ReacttheDiffAlgorithms and so onDiffWhat’s the difference between algorithms
  • keyinReactThe role of
  • How do I write high performanceReactcomponent

If you have questions about the React virtual DOM and the Diff algorithm, please read this article.

First let’s take a look at what the virtual DOM is:

Virtual DOM

In a native JavaScript program, we create and change the DOM directly, and DOM elements communicate with our application through events we listen for.

React converts your code into a JavaScript object, which in turn converts it into the real DOM. This JavaScript object is called the virtual DOM.

For example, the following HTML code:

<div class="title">
      <span>Hello ConardLi</span>
      <ul>
        <li>apple</li>
        <li>orange</li>
      </ul>
</div>
Copy the code

React might be stored as JS code like this:


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

When we need to create or update elements, React first creates and changes the VitrualDom object, and then renders the VitrualDom object into the real DOM.

When we need to listen for events on the DOM, we first listen for events on VitrualDom, which responds by proxying native DOM events.

Why use the virtual DOM

Why does React use VitrualDom?

Improve development efficiency

With JavaScript, our focus when writing applications is on how to update the DOM.

With React, you just tell React what state you want the view to be in, and React uses VitrualDom to make sure the DOM matches that state. You don’t have to do property manipulation, event handling, and DOM updates yourself; React does it for you.

This allowed us to focus on our business logic rather than DOM manipulation, which greatly improved our development efficiency.

About Improving Performance

There are a lot of articles that say VitrualDom can improve performance, but the claim is really one-sided.

It goes without saying that manipulating the DOM directly is very performance-intensive. React uses VitrualDom to manipulate the DOM.

VitrualDom doesn’t have any advantage when rendering for the first time, even though it does more computation and consumes more memory.

VitrualDom’s advantage lies in React’s Diff algorithm and batch strategy. React calculates how to update and render the DOM in advance of page updates. In fact, we can judge and implement this calculation process by ourselves when we operate DOM directly, but it will definitely cost a lot of energy and time. Moreover, it is often not as good as React. So React helped us “boost performance” in the process.

So, I’m more inclined to say that VitrualDom helps us develop more efficiently by helping us calculate how to update more efficiently when repeating renderings, not that it’s faster than DOM manipulation.

If you have any different views on this part of the analysis, feel free to comment in the comments section.

Cross-browser compatibility

React implements its own event mechanism based on VitrualDom. It simulates the event bubble and capture process, adopts event proxy, batch update and other methods, and smooths out event compatibility issues in various browsers.

Cross-platform compatibility

VitrualDom brings cross-platform rendering capabilities to React. Take React Native as an example. React draws the UI layer of the platform based on the VitrualDom, but the posture is different for different platforms.

Virtual DOM implementation principle

If you don’t want to read the tedious source code, or don’t have enough time right now, you can skip this chapter and go directly to 👇 virtual DOM principles and features summary

In the above figure, we continue to expand. According to the flow in the figure, we analyze the realization principle of the virtual DOM in turn.

JSX and createElement method

There are two encoding options for implementing a React component. The first option is to use JSX:

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

The second option is to write it directly with react. createElement:

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

JSX is just a function for React. CreateElement (Component, props,… Grammar sugar provided by the children) method. This means that all JSX code will eventually be converted to react.createElement (…). Babel helps us through this transformation process.

JSX as shown below

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

Will be converted by Babel to

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

Note that Babel compiles the first letter of a component in JSX. When the first letter is lowercase, it is considered a native DOM tag. The first variable of createElement is compiled as a string. When the initial letter is uppercase, it is considered a custom component, and the first variable of createElement is compiled as an object.

In addition, since JSX is compiled by Babel ahead of time, JSX cannot dynamically select a type at runtime, as shown in the following code:

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

It needs to be written like this:

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

So, to use JSX you need to install Babel plugin babel-plugin-transform-react-jsx

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

Creating a virtual DOM

Let’s take a look at what the virtual DOM actually looks like by printing the following JSX code on the console:

<div className="title">
      <span>Hello ConardLi</span>
      <ul>
        <li>apple</li>
        <li>orange</li>
      </ul>
</div>
Copy the code

React converts our code to this structure. Here’s a look at the createElement function (the source code is simplified).

The createElement function (props and child elements) returns a ReactElement object.

(1). Processing props:

  • 1. Add special attributesref,keyfromconfigAnd assign the value to
  • 2. Add special attributesself,sourcefromconfigAnd assign the value to
  • 3. Take out attributes except special attributes and assign values toprops

The purpose of these special attributes will be described in more detail in a later article.

(2). Get child elements

  • 1. Get the number of child elements — all arguments after the second argument
  • 2. If there is only one child element, assign toprops.children
  • 3. If there are multiple child elements, fill the child elements into an array and assign toprops.children

(3). Handle the default props

  • The static properties of the componentdefaultPropsDefault of definitionpropsFor the assignment

ReactElement

ReactElement combines several attributes passed in and returns.

  • type: The type of the element, which can be a native HTML type (string) or a custom component (function orclass)
  • key: Unique identifier of a component, used forDiffThe algorithm is described in more detail below
  • ref: Used to access nativedomnode
  • props: of the component passed inprops
  • owner: Currently under constructionComponentSubordinate to theComponent

? Typeof: an attribute we don’t usually see, which is assigned to REACT_ELEMENT_TYPE:

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

Visible,? Typeof is a variable of type Symbol, which prevents XSS.

This can be a problem if your server has a bug that allows the user to store arbitrary JSON objects and the client code requires a string:

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

A variable of type Symbol cannot be stored in JSON.

ReactElement. IsValidElement function is used to judge whether a React component is effective, here is the concrete implementation of it.

ReactElement.isValidElement = function (object) {
  return typeof object === 'object'&& object ! = =null && object.?typeof === REACT_ELEMENT_TYPE;
};
Copy the code

React render will render the React render. Typeof identifiers and components that fail rule verification are filtered out.

When your environment does not support Symbol,? Typeof is assigned to 0xeAC7, and the React developers give the answer as to why:

0xeAC7 looks a bit like React.

Self and source are added to objects only in non-production environments.

  • selfSpecifies which component instance is currently located.
  • _sourceSpecify the file from which the debug code comes (fileName) and lines of code (lineNumber).

The virtual DOM is converted to the real DOM

Here’s how React converts the virtual DOM to the real DOM.

The logic of this part is complicated, so let’s use the flow chart to sort out the whole process. The whole process can be roughly divided into four steps:

Procedure 1: Initial parameter processing

After writing our React component, we need to call reactdom.render (Element, container[, callback]) to render the component.

Internal actual call the render function _renderSubtreeIntoContainer, let’s take a look at its concrete implementation:

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

  • 1. Use the current componentTopLevelWrapperOn the parcel

TopLevelWrapper is just an empty shell that provides a rootID property for the component you need to mount and returns that component in the Render function.

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

The first argument to the reactdom. render function can be either a native DOM or React component, and a TopLevelWrapper wrapper will allow them to be handled uniformly in subsequent renderers regardless of whether they are native or not.

  • 2. Determine whether the element has been rendered at the root. If so, update or uninstall the element
  • 3. HandleshouldReuseMarkupVariable that indicates whether the element needs to be relabelled
  • 4. The call passes in the arguments processed above_renderNewRootComponentCalled after rendering is completecallback.

Call in _renderNewRootComponent instantiateReactComponent introduced to classify the components of the packaging to us:

React creates the following four categories of components based on their types:

  • ReactDOMEmptyComponent: an empty component
  • ReactDOMTextComponentText:
  • ReactDOMComponent: nativeDOM
  • ReactCompositeComponent: customReactcomponent

They all have the following three methods:

  • construct: Used to receiveReactElementInitialize.
  • mountComponent: used to generateReactElementCorresponding truthDOMorDOMLazyTree.
  • unmountComponentuninstallDOMNode, unbind event.

How to render is analyzed in Process 3.

Procedure 2: Batch, transaction invocation

Used in _renderNewRootComponent ReactUpdates. BatchedUpdates call batchedMountComponentIntoNode for batch processing.

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

Used in batchedMountComponentIntoNode transaction. Perform call mountComponentIntoNode let its based on transaction mechanism is called.

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

More on batch transactions in my previous analysis of the setState execution mechanism.

Process 3: Generate HTML

In mountComponentIntoNode function call ReactReconciler. MountComponent generate native DOM node.

Inside the mountComponent is actually a call to the mountComponent method of the four objects generated in procedure 1. Let’s start with the ReactDOMComponent:

  • 1. The specialDOMLabel,propsProcess.
  • 2. Create a label based on the label typeDOMNode.
  • 3. Call_updateDOMPropertieswillpropsInserted into theDOMNode,_updateDOMPropertiesCan also be used toprops DiffThe first parameter is from the last renderpropsThe second parameter is currentpropsIf the first parameter is empty, it is the first creation.
  • 4. Create oneDOMLazyTreeObject and call_createInitialChildrenRender the child node onto it.

So why not just generate a DOM node instead of creating a DOMLazyTree? Let’s take a look at what _createInitialChildren does:

The dangerouslySetInnerHTML attribute of the current node, whether the child node is text, and queueHTML, queueText, and queueChild of DOMLazyTree are called respectively.

DOMLazyTree is actually a wrapped object. The node property stores real DOM nodes. Children, HTML, and text store children, HTML, and text nodes, respectively.

It provides several methods for inserting children, HTML, and text nodes, all of which are conditionally inserted into DOMLazyTree objects when the enableLazy property is true, and into real DOM nodes when it is false.

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

EnableLazy is a variable that is true if the current browser is IE or Edge.

In Internet Explorer (8-11) and Edge browsers, it is much more efficient to insert nodes one by one without descendants than to insert an entire serialized node tree.

Therefore, lazyTree mainly solves the efficiency problem of node insertion in IE (8-11) and Edge browser, which will be analyzed in the following process 4: If the current is IE or Edge, the child nodes cached in DOMLazyTree need to be inserted recursively. Other browsers only need to insert the current node once because their children are already rendered without worrying about efficiency.

Let’s look at ReactCompositeComponent, because there is a lot of code here we will not post the code of this module, its internal mainly do the following steps:

  • To deal withprops,contexCall the constructor to create a component instance
  • Check whether the component is statelessstate
  • callperformInitialMountLife cycle, processing child nodes, getmarkup.
  • callcomponentDidMountThe life cycle

In the performInitialMount function, the componentWillMount life cycle is first called. Since the custom React component is not a real DOM, the mountComponent of the child node is also called. This is also a recursive process that returns markup and calls componentDidMount when all child nodes are rendered.

Process 4: Render HTML

Call the mountComponentIntoNode function to insert the markup generated in the previous step into the Container.

When the first rendering, _mountImageIntoNode will call DOMLazyTree. After the child nodes of the empty container insertTreeBefore:

Check whether it is a fragment node or a plug-in:

  • In both cases, insertTreeChildren is called to render the child node of this node to the current node, and then the rendered node is inserted into HTML

  • If it is any other node, insert the node into the HTML and then call insertTreeChildren to insert the child node into the HTML.

  • If the current node is not IE or Edge, there is no need to recursively insert the child node, only need to insert the current node once.

  • Judgment is notIEorbEdgewhenreturn
  • ifchildrenNot empty, recursiveinsertTreeBeforeTo insert
  • Render HTML nodes
  • Render text node

Native DOM event broker

I wrote an article about the React event mechanism of the virtual DOM. If you are interested, you can find the React event mechanism at 👇

Summary of virtual DOM principle and characteristics

The React component rendering process

  • Write React components using React. CreateElement or JSX. Virtually all JSX code will eventually be converted to React. CreateElement (…) Babel helps us through this transformation process.

  • The createElement function handles special props such as key and ref, assigns defaultProps to the defaultProps, and handles the passed child node to construct a ReactElement object (the so-called virtual DOM).

  • Reactdom.render renders the generated virtual DOM to the specified container, which uses batch processing, transaction mechanisms and browser-specific performance optimization, and finally converts to the real DOM.

The composition of the virtual DOM

The ReactElementelement object, our component will eventually be rendered as the following structure:

  • type: The type of the element, which can be a native HTML type (string) or a custom component (function orclass)
  • key: Unique identifier of a component, used forDiffThe algorithm is described in more detail below
  • ref: Used to access nativedomnode
  • props: of the component passed inprops.chidrenispropsProperty that stores the child nodes of the current component, either an array (multiple child nodes) or an object (only one child node)
  • owner: Currently under constructionComponentSubordinate to theComponent
  • self(Non-production environment) Specifies which component instance is currently located
  • _source(non-production) Specifies the file from which the debug code comes (fileName) and lines of code (lineNumber)

To prevent XSS

One more ReactElement object? Typeof property, which is a variable of Symbol type symbol. for(‘react.element’), when the environment does not support Symbol,? Typeof is assigned a value of 0xeAC7.

This variable prevents XSS. If your server has a bug that allows the user to store arbitrary JSON objects while the client code requires a string, this can pose a risk to your application. JSON cannot store a variable of type Symbol. React renders the variable without it. Components identified by Typeof are filtered out.

Batch processing and transactions

React uses batch and transaction mechanisms to render the virtual DOM to improve rendering performance.

Batch processing and transaction mechanisms are described in detail in my previous article React In-depth setState execution mechanism.

Targeted performance optimization

In Internet Explorer (8-11) and Edge browsers, it is much more efficient to insert nodes one by one without descendants than to insert an entire serialized node tree.

React uses lazyTree to render individual nodes in turn in IE (8-11) and Edge, whereas in other browsers the entire large DOM structure is first built and then inserted into the container as a whole.

Also, when rendering nodes individually, React takes into account special nodes such as fragments, which are not inserted one by one.

Virtual DOM event mechanism

React implements an event mechanism that maps all events bound to the virtual DOM to real DOM events, proxies all events to documents, simulates event bubbing and capturing, and implements uniform event distribution.

React constructs its own SyntheticEvent object, SyntheticEvent, which is a cross-browser native event wrapper. It has the same interface as browser native events, including stopPropagation() and preventDefault(), etc. They work the same in all browsers. This smoothes out event compatibility issues across browsers.

The first rendering process of the virtual DOM is only analyzed above, but it does not include the Diff process of the virtual DOM, which will be discussed in detail in the next article.

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

Recommended reading

  • 【React in-Depth 】setState execution mechanism
  • React Event mechanism
  • React Goes from Mixin to HOC to Hook

At the end of

The version in the source code of this article is act15, and there will be some differences with version 16. The changes of version 16 will be analyzed separately in the following articles.

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

Want to read more quality articles, download the mind mapping source file in the article, read the demo source, can pay attention to my Github blog, your star✨, praise and attention is my continuous creation of power!

I recommend you to follow my wechat public account [Code Secret Garden] and push high-quality articles every day. We can communicate and grow together.