As the saying goes, it’s better to know why you know what you know. I believe most people have used React before, but how much do they know about its mechanism? This article is designed to explain how React works. This article will not involve too much source code, but the contents are based on the source code (V16.13.1) analysis and come, we can put this article as a science article to read. Feel free to point out any errors in the comments section.

What’s the react

React describes itself on its website as a JavaScript library for building user interfaces. What it does is simply bridge the DOM node to you, shielding you from low-level operations and allowing you to change your user interface in an easier way.

So how did this bridge get built? In fact, it is very simple, even if it is complex, is the product of JS. So as long as you know js syntax, you can write react as well. But it’s all about how you organize your code and how you implement it. The cars we drive, for example, are complicated. You don’t really care what the internal structure is and how it’s organized. But take the pieces apart and put them in front of you, and you know, oh, this is iron, this is rubber, I know all the pieces. At the same time, to understand the internal principle of the car to better drive, meet what small problems to repair their own.

So today, let’s take react apart and see how it actually renders our code to the browser.

Taking the huanglong

Let’s skip the initial fancy steps and look at the last actions react does. Is removeAttribute familiar? Is removeChild familiar? At the end of the day, it’s going to come back to these basic apis, so don’t be afraid of it, it’s just that.

if (value === null) {
  node.removeAttribute(_attributeName);
} else {
  node.setAttribute(_attributeName,  ' ' + value);
}

while (node.firstChild) {
  node.removeChild(node.firstChild);
}

while (svgNode.firstChild) {
  node.appendChild(svgNode.firstChild);
}
Copy the code

Dry goods start

React: What did react do?

From JSX to the user interface

The most common thing we wrote in React was JSX. It’s nothing. You can think of it as a rule of react, and if you write it like it says, it understands what you’re writing, but if you don’t write it like it doesn’t understand what you’re writing.

JSX to JS objects

What will React do with JSX we wrote? It will use a function called createElement to turn what we’ve written into a fixed structure

var element = {
  // This tag allows us to uniquely identify this as a React Element
  $$typeof: REACT_ELEMENT_TYPE,
  // Built-in properties that belong on the element
  type: type,
  key: key,
  ref: ref,
  props: props,
  // Record the component responsible for creating this element.
  _owner: owner
};
Copy the code

This thing inside React is like a DOM node. So why do we write JSX?

You can create a node manually by calling createElement, or react will accept only the element notation for a component. JSX = JSX; createElement = JSX; createElement = JSX;

Js object to Fiber object

What is the Fiber object? It’s essentially a JS object. But it’s more complicated.

function FiberNode(tag, pendingProps, key, mode) {
  // Instance
  this.tag = tag;
  this.key = key;
  this.elementType = null;
  this.type = null;
  this.stateNode = null; // Fiber

  this.return = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;
  this.ref = null;
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;
  this.mode = mode; // Effects

  this.effectTag = NoEffect;
  this.nextEffect = null;
  this.firstEffect = null;
  this.lastEffect = null;
  this.expirationTime = NoWork;
  this.childExpirationTime = NoWork;
  this.alternate = null;

  {
    // Note: The following is done to avoid a v8 performance cliff.
    //
    // Initializing the fields below to smis and later updating them with
    // double values will cause Fibers to end up having separate shapes.
    // This behavior/bug has something to do with Object.preventExtension().
    // Fortunately this only impacts DEV builds.
    // Unfortunately it makes React unusably slow for some applications.
    // To work around this, initialize the fields below with doubles.
    //
    // Learn more about this here:
    // https://github.com/facebook/react/issues/14365
    // https://bugs.chromium.org/p/v8/issues/detail?id=8538
    this.actualDuration = Number.NaN;
    this.actualStartTime = Number.NaN;
    this.selfBaseDuration = Number.NaN;
    this.treeBaseDuration = Number.NaN; // It's okay to replace the initial doubles with smis after initialization.
    // This won't trigger the performance cliff mentioned above,
    // and it simplifies other profiler code (including DevTools).

    this.actualDuration = 0;
    this.actualStartTime = -1;
    this.selfBaseDuration = 0;
    this.treeBaseDuration = 0;
  } // This is normally DEV-only except www when it adds listeners.
  // TODO: remove the User Timing integration in favor of Root Events.


  {
    this._debugID = debugCounter++;
    this._debugIsCurrentlyTiming = false;
  }

  {
    this._debugSource = null;
    this._debugOwner = null;
    this._debugNeedsRemount = false;
    this._debugHookTypes = null;

    if(! hasBadMapPolyfill &&typeof Object.preventExtensions === 'function') {
      Object.preventExtensions(this); }}}Copy the code

Each component is a Fiber object, and each component is connected to a fiber tree, which is similar to the DOM tree. Why a Fiber tree? Have you ever heard of the Diff algorithm? Fiber trees are JS objects, so manipulating them is much faster than manipulating DOM nodes. We put the diff operation on the Fiber tree, and finally we can map the nodes that need to be updated to the real DOM to optimize performance.

In fact, the most important reason is that React specifically launched a new architecture to avoid being unable to respond to other events due to occupying JS threads during the calculation of Virtual Dom. Today, we will not talk about this content here, but first we will make clear the main process of React.

Fiber object diff algorithm

What is the purpose of the Diff algorithm? Figure out what fiber nodes you can reuse to save time on creating new ones, and figure out what really needs updating. We speak in pictures

Here are five components, which together form a Fiber tree, and after a setState, component D becomes component E, which looks like this

How will the second Fiber tree be generated? React/DIff/diff/diff/DIff/DIff/DIff/DIff/DIff/DIff React regenerates a fiber node for D instead of E.

So what is the process of diff?

Simply put, react will reuse a single node as long as its key and type are the same. The default is null if no key is given. Null === null for internal comparisons, so it will be reused even if the type is the same.

If there are multiple nodes, the key and type are also compared. If no key is given, index is used as the key by default. In this case, in addition to the sequential comparison of key and type, the optimization is also made for the situation of node changing position. The corresponding node will be found in the list according to the key value and reused.

As you can see, React tries to reuse old nodes as much as possible to avoid creating new ones. By multiplexing, I mean reusing the fiber nodes before, but I don’t mean that the real DOM won’t be updated.

For more information, please refer to my previous post

New Fiber tree to update linked list

We call the fiber tree in the back the new Fiber tree.

forreactEverything isprops, such asDOMOn the nodestyle,src,data-propsWait, so did the contents of the packageprops.children. soreactIn generating newfiberAfter, will go to compare old and newpropsIf there is a change, it will be added to the update list. What is an update linked list? Again, let’s use pictures

Fiber has an attribute called nextEffect that connects nodes that need to be updated, so when react detects changes in props, it adds them to the link.

The final render

Finally react goes to the Commit phase, where it iterates through the update list and actually updates it to the user interface. At this point, the render process is complete and the interface is updated.

In our case, React will end up updating components A, B, and D.

This is where React calls the JS DOM manipulation API to update the user interface.

Optimization of rendering process

Avoid duplicate rendering

We have combed the whole rendering process of React above, so it is impossible to update the whole fiber tree when we update the state. When will the update process of React be triggered?

As long aspropsIf it changes, it triggersreactUpdate process of

Here’s an example:

class App extends Component {
  state = {data: "hello world".src: logo};

  data = {data: 'test'}

  render() {
    return (
      <div>
        <Item data={{data: this.data}} / >
        <button onClick={()= > this.setState({data: "new data", src: logo2})}>setState</button>
      </div>); }}export default App;
Copy the code

If I hit setState, will the data that I pass to the Item be updated? Data is an object hanging on this, and it will not change, so the props we gave twice will not change.

But the truth is that the Item component goes through the diff process again every time. Why? Remember that JSX calls a createElement function, which wraps another layer around the props, generating a new props. So even though the value we passed to Item didn’t change, it still changed when we passed it to React.

This problem is similar to the Context repeat rendering problem, so let’s look at the following code:

//app.js
class App extends React.Component {
  state = {
    data: 'old data'
  }

  changeState = data= > {
    this.setState({
      data
    })
  }

  render() {
    return (
      <CustomContext.Provider value={{data: this.state.setData: this.changeState}} >
        <div className="App">
          <ContextContent />
          <OtherContent />
        </div>
      </CustomContext.Provider>); }}export default App;

//ContextContent.js
export default class ContextContent extends Component {
  render() {
    console.log("Context render")
    return <CustomContext.Consumer>
        {value => (
          <div>
            <div>{value.data.data}</div>
            <button onClick={()= > value.setData(Math.random())}>change</button>
          </div>
        )}
      </CustomContext.Consumer>}}//OtherContent.js
export default class OtherContent extends Component {
  render() {
    console.log("otherContent render")
    return (
      <>
        <div>other content</div>
      </>)}}Copy the code

We pass values to the components inside the Context, and when we change the state, of course, we only want the components that use that value to be rerendered, but what does the current way of writing this result?

As you can see, every time we change state, another unrelated component is updated. Why is that? Because we called createElement each time, we generated a new props each time (even though we didn’t give any props here, the react props was actually generated internally at createElement).

So how can the problem be solved? We want to make sure that the props don’t change, that is, we don’t want to call createElement every time an unrelated component calls it. I won’t give you the solution here, so let’s think about it.

This is why Context can have performance problems when used incorrectly. If you give a Provider in the outermost layer and change its Value frequently, the result will be to re-diff the entire Fiber tree each time.

Note that I said re-diff here, not re-render. React ends up finding that nothing has changed, so it doesn’t rerender the component, which means it doesn’t update the user interface. But the react time-consuming part is actually in the diff, which you can interpret as you doing a lot of work to figure out where the component needs to be updated, and then concluding that the component doesn’t need to be updated.

Add a key value to the component

In fact, as we mentioned earlier, React tries to reuse fiber nodes as much as possible. So even if you don’t add keys specifically, react will reuse nodes as long as you don’t arbitrarily change the DOM structure.

When rendering array structures, React uses index as the key by default. If you change the position of nodes arbitrarily, the React optimization may fail. In this case, we need to give each node a key that truly represents itself, so react can recognize them if the order changes.

So what does it do with a key? Will it re-render if the key stays the same?

React will try to reuse the existing nodes when building the Fiber tree. So the optimization is that the diff process builds nodes faster, and then compares props as usual and adds to the update list.

Is there a way to tell React that I haven’t changed anything about this component and don’t touch it. ShouldComponentUpdate does just that.

shouldComponentUpdate

Adding a key to the component works, but React ends up going through the diff process.

ShouldComponentUpdate () shouldComponentUpdate () shouldComponentUpdate () shouldComponentUpdate () shouldComponentUpdate () shouldComponentUpdate () shouldComponentUpdate () shouldComponentUpdate () shouldComponentUpdate ()

A simple optimization can be achieved by inheriting the PurComponent. After inheriting the PurComponent, a shallow comparison is made to the props, and the diff update process will not be re-implemented if the props are not changed.

The same effect can be achieved with react. Memo when using the function component.

For example, I modified the Context example to PurComponent

//otherContent.js
export default class OtherContent extends PureComponent {
  render() {
    console.log("otherContent render")
    return (
      <>
        <div>other content</div>
      </>)}}Copy the code

Let’s look at the effect

So the otherContent component doesn’t have to go through the diff process again every time it changes.

React or native who is faster?

People still think React will be faster than native. React goes all the way from JSX to Fiber to rendering to the interface. It’s impossible to update the user interface faster than when you call js directly.

We developed React instead of native, not because it was fast, but because it was convenient. We can use React to develop new products more quickly and efficiently, and the React virtual DOM is fast because it provides us with this convenience while maintaining certain performance. But it will certainly be slower than if you just write the original.

Of course, if you write your code directly with react, it won’t be as maintainable, readable, or efficient

conclusion

React renders code to the browser. There are a lot of details that we haven’t covered in this article. Let’s use another diagram to summarize the react process

Actually, the first render is slightly different from the subsequent render. I don’t make much of a distinction here, but the general flow is like this.

Also, if there are any mistakes in the article, please point them out in the comments section