This is the 13th day of my participation in the August More text Challenge. For details, see: August More Text Challenge

preface

In a previous post, is React setState asynchrony really just about performance? For some of the deeper reasons why React setState is asynchronous, maintaining consistency and starting concurrent updates for future architecture upgrades that are needed. After the article was published, I also received a thought from an upperclass, the original words were “Besides this, we can think about what is the Web. It is impossible to synchronize from the original top-level design, and the later Fiber is also to solve the idle problem, and finally complete the perfect scheduling of resources”. This sentence also gives a deeper insight, here Thanksgiving, the follow-up will be from these angles of deep mining. After yesterday’s talk about why React setState is asynchronous, today we’re going to talk about the beauty of React setState synchronous asynchrony.

  • When do you synchronize?
  • When asynchronous?
  • Why is there synchronization?

1. SetState

This question is not only frequently used in the interview of BAT factories, but also in various kinds of articles, as follows:

import React from "react";
import "./styles.css";
export default class App extends React.Component{
  state = {
    count: 0
  }
    // count +1
  increment = () = > {
    console.log(Increment setState;.this.state.count)
    this.setState({
      count: this.state.count + 1
    });
    console.log(Increment setState; increment setState;.this.state.count)
  }

  // count +1 3 times
  triple = () = > {
    console.log(Count before 'triple setState '.this.state.count)
    this.setState({
      count: this.state.count + 1
    });
    this.setState({
      count: this.state.count + 1
    });
    this.setState({
      count: this.state.count + 1
    });
    console.log('Triple setState after count'.this.state.count)
  }
    // count - 1
  reduce = () = > {
    setTimeout(() = > {
      console.log('Reduce count before setState'.this.state.count)
      this.setState({
        count: this.state.count - 1
      });
      console.log('Reduce count after setState'.this.state.count)
    }, 0);
  }
  render(){
    return <div>
      <button onClick={this.increment}>+ 1</button>
      <button onClick={this.triple}>+ 1 three times</button>
      <button onClick={this.reduce}>- 1</button>
    </div>}}Copy the code

The test code address: codesandbox. IO/s/setstate -…

Click the three buttons from left to right, and if you can get the results in your mind quickly, you have a good idea of setState synchronization and asynchrony. It’s clear from the very beginning of this API that setState is an asynchronous method, that when we execute setState, we don’t immediately trigger an update of the state, so we print 0 both times in increment. Although the triple function performs three setstates, the batch update collects the same operation three times and becomes one update operation, with the asynchrony of setState added, so the value of the triple output is only the value 1 after the last change in the first step. Let’s look at the third function reduce. If you’re a React beginner you might be a little confused that setState is a synchronous update. Don’t doubt it. For an old hand, you may know exactly why, sometimes synchronous updates, sometimes asynchronous updates, today we will talk about setState asynchronous synchronous updates (principle).

2. The charm of asynchrony, the art of batch operation

Regardless of synchronous asynchrony, what does React do after the setState is called? If you are familiar with the history of the React version updates, there may be some minor differences after the React version setSatate triggers, but the overall idea is the same.

React15

  • Trigger setState
  • shouldComponentUpdate
  • componentWillUpdate
  • render
  • componentDidUpdate

React16.3

  • Trigger setState
  • shouldComponentUpdate
  • render
  • getSnpshotBeforeUpdate
  • componentDidUpdate

React16.4

  • Trigger setState
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate

As you can see from this simple flowchart, when the setState is triggered, there is a complete update process that involves several steps including re-render. Re-render itself involves manipulating the DOM, which has a significant performance overhead. If it’s true that “a setState triggers a complete update process,” then every setState call triggers a re-render, and our view is likely to get stuck before it’s refreshed several times.

this.setState({
  count: this.state.count + 1
}); / / triggers the re - render
this.setState({
  count: this.state.count + 1
}); / / triggers the re - render
this.setState({
  count: this.state.count + 1
}); / / triggers the re - render.// The page is stuck
Copy the code

Now you probably know why setStates need to be done in batches. One important reason is to avoid frequent rerenders. The internal mechanism is similar to Vue’s $nextTick and the browser’s event-loop. Multiple setStates are executed, and they are stored in a queue. When the operation (the current synchronous operation) is completed, the states to be stored in the queue are merged. So no matter how many times you perform setState, you only end up with one update for the latest state value, which is called batch operation update.

this.setState({
  count: this.state.count + 1
});
// Enter queue [count + 1]
this.setState({
  count: this.state.count + 1
});
// Enter queue [count + 1, count + 1]
this.setState({
  count: this.state.count + 1
});
[count + 1, count + 1, count + 1].// Merge state[count + 1]
// Execute count + 1
Copy the code

You may already know about asynchronous updates and batch updates in React, but the fun is still to come.

3. Composite events

Before analyzing the synchronous scene, we need to add a very important knowledge point, that is, the synthesis event of React, which is also an easy point to be examined in the React interview. This paper just introduces the synthesis event of React, and will write a special article about the synthesis event of React later. Before we talk about synthetic events, let’s talk about our original event delegate, which appears more for performance purposes. Here’s an example:

<div> <div onclick="geText(this)">text 1</div> <div onclick="geText(this)">text 2</div> <div onclick="geText(this)">text  3</div> <div onclick="geText(this)">text 4</div> <div onclick="geText(this)">text 5</div> // ... 16~9999 <div onclick="geText(this)">text 10000</div> </div>Copy the code

Suppose a large div tag is followed by 10,000 smaller div tags. Now you need to add a click event to get the text in the current DIV tag. How do you do that? The easiest thing to do is to add an onclick event to each internal div tag. If there are 10,000 div tags, 10,000 events will be added. This is a very unfriendly approach and can have an impact on the performance of the page. So event delegation plays a big role. This is solved by binding the event to a large div tag on the outside. When the internal div clicks, it is triggered by the event bubble to the parent tag, and in the tag’s onclick event, it is determined which tag triggered the click event.

React reacts to the document with an event listener. DOM event emitted after bubbling to document; React finds the corresponding component and creates a composite event. And simulate the event bubble by component tree. There is only one version of React on a page. If there are multiple versions, the event becomes messy.

After version 17, however, this problem has been resolved and the event delegate is not mounted on the Document, but on the DOM container, which is the node called by reactdom.render.

Synthetic events have a lot to do with triggering setState updates, and we can’t talk about synchronous setstates until we understand synthetic events.

4. The story behind synchronization

Going back to the previous example, setState has the “function” of synchronization under the “wrapper” of the setTimeout function. Why does setTimeout change the execution order of setState from asynchronous to synchronous?

How setState works

From a source perspective, let’s see how setState actually works. Note that the React version of the source code is React 15.

/ / the entry
ReactComponent.prototype.setState = function (partialState, callback) {
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState'); }};Copy the code
enqueueSetState: function (publicInstance, partialState) {
  // Get the component instance
  var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
  // This queue corresponds to an array of states for each component instance
  var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
  // Put the new state in the component's state queue
  queue.push(partialState);
  // enqueueUpdate is used to process the current component instance
  enqueueUpdate(internalInstance);
}
Copy the code
function enqueueUpdate(component) {
  ensureInjected();
  // isBatchingUpdates specifies whether or not the component is being batch created/updated
  if(! batchingStrategy.isBatchingUpdates) {// Update components immediately if they are not currently in the batch create/update phase
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }
  // Otherwise, put the component in the dirtyComponents queue and let it "wait".
  dirtyComponents.push(component);
  if (component._updateBatchNumber == null) {
    component._updateBatchNumber = updateBatchNumber + 1; }}Copy the code
var ReactDefaultBatchingStrategy = {
  // Globally unique lock identity
  isBatchingUpdates: false.// The method that initiates the update action
  batchedUpdates: function(callback, a, b, c, d, e) {
    // Cache the lock variable
    var alreadyBatchingStrategy = ReactDefaultBatchingStrategy. isBatchingUpdates
    // Lock the lock
    ReactDefaultBatchingStrategy. isBatchingUpdates = true
    if (alreadyBatchingStrategy) {
      callback(a, b, c, d, e)
    } else {
      // Start the transaction and put the callback into the transaction to execute
      transaction.perform(callback, null, a, b, c, d, e)
    }
  }
}
Copy the code

The isBatchingUpdates property in the source code directly determines whether to go through the update process or wait in a queue. The batchedUpdates method can initiate the update process directly. It’s safe to assume that batchingStrategy is the object used to control batch updates within React.

“Lock” on isBatchingUpdates

IsBatchingUpdates defaults to False, meaning “no batch updates are currently being performed.” When React calls batchedUpdate to perform an update action, it will first “lock” (set to true), indicating that it is “currently in the batch update process”. When the lock is “locked”, any component that needs to be updated can only be temporarily queued in dirtyComponents for the next batch update, rather than “jumping the queue” at will. The idea of “task locking” embodied here is the cornerstone of React’s ability to implement ordered batching in the face of a large number of states.

In events such as onClick, onFocus, etc., you can update the status of isBatchingUpdates to true because the composite event encapsulates a layer; In the React lifecycle function, the status of isBatchingUpdates can also be updated to true. In React’s own lifecycle events and composite events, you can take control of isBatchingUpdates and queue state to control the execution pace. With external native events, there is no outer encapsulation and interception to update isBatchingUpdates to true. This causes the status of isBatchingUpdates to only be false and execute immediately. So native events like addEventListener, setTimeout, and setInterval are updated synchronously.

conclusion

The reason is very simple, but the principle is very complicated. Rather than being purely synchronous/asynchronous, setState behaves differently depending on the scenario in which it is called: It behaves asynchronously in React hook functions and synthetic events; In setTimeout, setInterval, and other functions, including DOM native events, it is synchronized. This difference, in essence, is determined by how the React transaction mechanism and the batch update mechanism work.

reference

  • The projects. Wojtekmaj. Pl/react – lifec…
  • zhuanlan.zhihu.com/p/25882602
  • Kaiwu.lagou.com/course/cour…
  • Kaiwu.lagou.com/course/cour…