Transferred from IMWeb community by Qiong Huang,
The original link

preface

If you learn react, setState is a very important method to update the state of our data. This article will reveal the mystery of setState from simple use to deep inside setState

Precautions for using setState

The setState(updater, callback) method is used to tell the React component that the data has been updated and may need to be rerendered. It’s asynchronous. React usually gathers a batch of components that need to be updated and updates them all at once to ensure rendering performance, so it’s a bit of a trap:

After using setState to change the state, using this.state immediately to get the latest state is often not available.

The point a

So the first use point is: if you want to do business based on the latest state, you can get it in the callback function componentDidUpdate or setState. (Note: Official recommendation for the first practice)

// setState callback function changeTitle:function (event) {
  this.setState({ title: event.target.value }, () => this.APICallFunction());
},
APICallFunction: function () {
  // Call API with the updated value
}
Copy the code

Point 2

Imagine a requirement that needs to be added twice in onClick, as follows

  onClick = () => {
    this.setState({ index: this.state.index + 1 });
    this.setState({ index: this.state.index + 1 });
  }
Copy the code

In React’s eyes, this approach will eventually become

Object.assign(
  previousState,
  {index: state.index+ 1},
  {index: state.index+ 1},
  ...
)
Copy the code

Because later data overrides previous changes, you only end up adding them once. If the next state depends on the previous state, it is recommended to pass the function to setState

onClick = () => {
    this.setState((prevState, props) => {
      return {quantity: prevState.quantity + 1};
    });
    this.setState((prevState, props) => {
      return {quantity: prevState.quantity + 1};
    });
}
Copy the code

With those two considerations for using setState, let’s look at the process of updating a component once the setState is called. Here is a simple flowchart.

Let’s break down the flow of the diagram step by step.

A, setState

ReactBaseClassses.js

ReactComponent.prototype.setState = function(partialState, callback) {// willsetThe State affairs in the queue for this. The updater. EnqueueSetState (this, partialState);if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState'); }};Copy the code

The partialState can be passed either as an object or as a function. It produces a new state that is merged with the old state in a way called object.assgine ().

Second, the enqueueSetState

  enqueueSetState: function(publicInstance, PartialState) {/ / get the current component instance var internalInstance = getInternalInstanceReadyForUpdate (publicInstance,'setState'); / / will update the state into an array of var queue. = internalInstance _pendingStateQueue | | (internalInstance. _pendingStateQueue = []); queue.push(partialState); EnqueueUpdate (internalInstance); // Put the component instance to be updated in a queue. }Copy the code

This code tells us that enqueueSetState does two things: 1, put the new state into the array, and 2, use enqueueUpdate to handle the instance object to be updated

Third, enqueueUpdate

ReactUpdates.js

functionEnqueueUpdate (Component) {// Process update State transactions if not in the batch create/update component phaseif(! batchingStrategy.isBatchingUpdates) { batchingStrategy.batchedUpdates(enqueueUpdate, component);return; } // If you are in the process of creating/updating components in batches, put the current component in the dirtyComponents array dirtyComponents. Push (component); }Copy the code

As you can see from this code, if you are in the process of creating/updating a component, you will not update the component immediately. Instead, you will put the current component in the dirtyComponent first, so not every setState will update the component.

This code explains what we’ve often heard: setState is an asynchronous process that gathers a batch of components that need to be updated and updates them together.

What is a batchingStrategy?

Four, batchingStrategy

ReactDefaultBatchingStrategy.js

Var ReactDefaultBatchingStrategy = {/ / used to mark is currently out of the batch update isBatchingUpdates:false// When this method is called, we officially start batchedUpdates:function (callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

    ReactDefaultBatchingStrategy.isBatchingUpdates = true; // If the current transaction is in the process of updating, then callback is called, which is enqueueUpdateif (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else{// Otherwise execute the update transactionreturntransaction.perform(callback, null, a, b, c, d, e); }}};Copy the code

If the current transaction is in the process of updating, use enqueueUpdate to place the current component in the dirtyComponent. 2. Execute the update transaction if the current update process is not in progress.

Five, the transaction

/** * <pre> * wrappers (injected at creation time) * + + * | | * +-----------------|--------|--------------+ * | v | | *  | +---------------+ | | * | +--| wrapper1 |---|----+ | * | | +---------------+ v | | * | | +-------------+ | | * | | +----| wrapper2 |--------+ | * | | | +-------------+ | | | * | | | | | | * | v v v v | wrapper * | +---+ +---+ +---------+ +---+ +---+ | invariants * perform(anyMethod) | | | | | | | | | | | | maintained * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|--------> * | | | | | | | | | | | | * | | | | | | | | | | | | * | | | | | | | | | | | | * | +---+ +---+ +---------+ +---+ +---+ | * | initialize close | * +-----------------------------------------+ * </pre> */Copy the code

Perform the initialize method of all the wrappers before anyMethod is executed. After anyMethod is executed, perform the initialize method of all the wrappers. Executing all of the wrapper’s close methods is incredibly easy.

Js in ReactDefaultBatchingStrategy., tranction wrapper has two FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES

var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function () {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false; }}; var FLUSH_BATCHED_UPDATES = { initialize: emptyFunction, close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates) }; var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];Copy the code

As you can see, neither wrapper initialize does anything, but after the callback is executed, What RESET_BATCHED_UPDATES does is set isBatchingUpdates to false, FLUSH_BATCHED_UPDATES is going to be used for flushBatchedUpdates, and then it’s going to loop through all the dirtyComponents, call updateComponent to execute all the lifecycle methods, ComponentWillReceiveProps shouldComponentUpdate, componentWillUpdate, render, componentDidUpdate last updated implementation component.

This is the implementation process of setState. Finally, I will summarize it with a flowchart

Reference documents:

  1. zhuanlan.zhihu.com/p/25882602