First, please read the official document

SetState () = setState() = setState(

React

React 英 文 版

2. Practice and problems of setState(

Let’s start with the simplest question, is count plus 2 when I click the button?

class NextPage extends Component<Props> {
  static navigatorStyle = {
    tabBarHidden: true
  };

  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  add() {
    this.setState({
      count: this.state.count + 1
    });
    this.setState({
      count: this.state.count + 1
    });
  }

  render() {
    return( <View style={styles.container}> <TouchableOpacity style={styles.addBtn} onPress={() => { this.add(); Style ={styles.btnText}> click +2</Text> </TouchableOpacity> </ style={styles.btnText} {this.state.count}</Text> </View> ); }}Copy the code

It turns out to be 1




Why do I only add 1?

Look at this sentence on the official website

setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.

SetState () does not always update the component immediately; it may batch or delay the update. This makes it a potential hazard to read this.state immediately after calling setState (). Just throw the correct answer by pressing the button plus 2. Either way is OK

this.setState(preState => {
  return {
    count: preState.count + 1
  };
});
this.setState(preState => {
  return {
    count: preState.count + 1
  };
});
Copy the code

setTimeout(() => {
  this.setState({
    count: this.state.count + 1
  });
  this.setState({
    count: this.state.count + 1
  });
}, 0);
Copy the code




SetState source code world

SetState () is a synchronous and asynchronous method, so when is it synchronous and when is it asynchronous?

Go to the source code to see the implementation is more reliable way.

SetState () is asynchronous. SetState () is asynchronous. SetState () is asynchronous, and setState() is asynchronous.

1, How to quickly view the React source code

Clone the react github repository

The react – making the warehouse

git clone https://github.com/facebook/react.git
Copy the code


The latest version I’ve seen so far is 16.2.0, and I chose the 15.6.0 code

One is to refer to the analytical results of predecessors

Secondly, my level is limited. If my writing is really not clear, students can refer to other people’s analysis articles to read together, so as not to completely understand

How do I switch versions?

1. Find the corresponding version number



2. Copy the historical record number in 15.6.0



3, rolled back

git reset --hard 911603b
Copy the code

As shown, the successful rollback to version 15.6.0




2. SetState => enqueueSetState

Core principle: since it is to see the source code, of course, it is not line by line to read the code, but to see the core idea, so the next code will only put the core code, side branch section only mention or ignore



SetState entry files in SRC/isomorphic/modern/class/ReactBaseClasses. Js

The React component inherits from react.component.setState is react.component.component.setState, therefore, belongs to the component’s prototype method

ReactComponent.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState'); }};Copy the code

PartialState as the name implies — “partialState”, this name probably means not to affect the original state


When we call setState we’re actually calling the enqueueSetState method, so let’s follow through (I’m using vscode’s global search), Found this file SRC/renderers/Shared/stack/reconciler/ReactUpdateQueue. Js



This file exports a ReactUpdateQueue object, “React update queue”, which is nicely named and annotated, and registers the enqueueSetState method


3. EnqueueSetState => enqueueUpdate

First look at the definition of enqueueSetState

  enqueueSetState: function(publicInstance, partialState) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'setState',); var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []); queue.push(partialState); enqueueUpdate(internalInstance); },Copy the code

There are only two properties of internalInstance to focus on

_pendingStateQueue: indicates the queue to be updated

_pendingCallbacks: Updates the callback queue

If the value of _pendingStateQueue is null, assign it an empty array [] and place the partialState in the pending state queue _pendingStateQueue. Finally, enqueueUpdate(internalInstance)

Next look at enqueueUpdate

function enqueueUpdate(internalInstance) {
  ReactUpdates.enqueueUpdate(internalInstance);
}
Copy the code

It executes the enqueueUpdate method of ReactUpdates

var ReactUpdates = require('ReactUpdates');
Copy the code

This file just beside SRC/renderers/Shared/stack/reconciler/ReactUpdates. Js

Find the enqueueUpdate method



Are defined as follows

function enqueueUpdate(component) {
  ensureInjected();

  if(! batchingStrategy.isBatchingUpdates) { batchingStrategy.batchedUpdates(enqueueUpdate, component);return;
  }

  dirtyComponents.push(component);
  if(component._updateBatchNumber == null) { component._updateBatchNumber = updateBatchNumber + 1; }}Copy the code


This code is very important for understanding setState

if(! batchingStrategy.isBatchingUpdates) { batchingStrategy.batchedUpdates(enqueueUpdate, component);return;
  }
dirtyComponents.push(component);
Copy the code

Judge batchingStrategy. IsBatchingUpdates batchingStrategy is batch update strategy, whether isBatchingUpdates said in the batch update process, start the default value is false


The above sentence means:

If you are in batch update mode (isBatchingUpdates are true), instead of updating state, add the component to the dirtyComponents array.

If you are not in batch update mode, execute the batchedUpdates method on all the updates in the queue. If you look down, batch component updates in transaction mode, as shown below.

Borrow the image from Page167 in The React Technology Stack




BatchedUpdates => Call Transaction

The batchingStrategy. IsBatchingUpdates is how to return a responsibility? It seems to be the key

BatchingStrategy object, however, was not good looking for, it is through the injection method of injection, to find, is found batchingStrategy ReactDefaultBatchingStrategy.

SRC/renderers/Shared/stack/reconciler/ReactDefaultBatchingStrategy js, specific how to find the files, and belong to the category of another, we focus solely on setState today, other later to say again


I believe some of you may be a little confused by this, it doesn’t matter, hold on a little longer, ignore the last part, just know that we have found the core method of batchedUpdates, we are going to win, don’t give up (I also survived the first time like this, if not once, I will watch it twice, if not more times)


Let’s start with the batch update strategy -batchingStrategy. What is it

var ReactDefaultBatchingStrategy = {
  isBatchingUpdates: false,

  batchedUpdates: function(callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

    ReactDefaultBatchingStrategy.isBatchingUpdates = true;

    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
      returntransaction.perform(callback, null, a, b, c, d, e); }}}; module.exports = ReactDefaultBatchingStrategy;Copy the code

Here we have the isBatchingUpdates property and the batchedUpdates method


Perform () If isBatchingUpdates are true and currently in the update transaction state, store the Component in the dirtyComponent, otherwise call batchedUpdates and initiate a transaction.perform()

Note: All batchUpdate functionality is implemented by performing various transactions

This is the concept of transactions, so let’s look at transactions first


5, transaction

The React technology stack Page169




Simply put, a Transaction is a method that needs to be executed wrapped in a Wrapper and executed using the Perform method provided by Transaction. Before performing, initialize methods in all wrappers are executed; Perform all of the close methods after perform. A set of initialize and close methods is called a wrapper, and as you can see from the example above, Transaction supports multiple wrapper stacks.


Transaction in React provides a Mixin for other modules to implement their own transactions. To use Transaction modules, in addition to mixing Transaction mixins into your own Transaction implementation, you also need to implement an abstract getTransactionWrappers interface. This interface is used by Transaction to get all the front and close methods that need to be wrapped, so it needs to return an array of objects, each with a method key of initialize and close.


The following code should help

var Transaction = require('./Transaction'); Var MyTransaction =function() {
  // do sth.
};

Object.assign(MyTransaction.prototype, Transaction.Mixin, {
  getTransactionWrappers: function() {
    return [{
      initialize: function() {
        console.log('before method perform');
      },
      close: function() {
        console.log('after method perform'); }}]; }; }); var transaction = new MyTransaction(); vartestMethod = function() {
  console.log('test');
}
transaction.perform(testMethod);

// before method perform
// test
// after method perform
Copy the code


6. Core analysis: batchingStrategy Batch update strategy

Go back to batchingStrategy: Batch update strategy and look at its code implementation

var ReactDefaultBatchingStrategy = {
  isBatchingUpdates: false,

  batchedUpdates: function(callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

    ReactDefaultBatchingStrategy.isBatchingUpdates = true;

    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
      returntransaction.perform(callback, null, a, b, c, d, e); }}};Copy the code


As you can see, isBatchingUpdates will have an initial value of false. The isBatchingUpdates variable will be set to true when the batchedUpdates method is called. The different processes are then performed based on the values of isBatchingUpdates before setting them


Remember that important piece of code

if(! batchingStrategy.isBatchingUpdates) { batchingStrategy.batchedUpdates(enqueueUpdate, component);return;
  }
dirtyComponents.push(component);
Copy the code

1. First of all, the click event itself is being processed in a larger transaction (just remember that), and isBatchingUpdates are already true


2, call setState (), call the ReactUpdates. BatchedUpdates transaction ways for event handling


3. SetState pushes isBatchingUpdates into the dirtyComponents array when they are already true.


4, at the end of this transaction. Only through ReactUpdates flushBatchedUpdates method all the temporary state merge and calculate the current props and state, then will be executed batch close end the transaction.


Here I did not follow the ReactUpdates speaks flushBatchedUpdates method, this part involves the rendering and the content of the Virtual Dom, anyway, you know it is used to perform rendering.

So far, the core concept of setState has been relatively clear, and the next content, for the time being, it is ok to know first, otherwise it will be too complicated to expand one ring and one ring, we should grasp the core when doing things.


I wonder if any of you came up with a question?

The isBatchingUpdates flag bit was set to true when batchedUpdates were initiated. When was it set to false?

Remember the above transaction close method, the same file SRC/renderers/Shared/stack/reconciler/ReactDefaultBatchingStrategy. Js

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];function ReactDefaultBatchingStrategyTransaction() {
  this.reinitializeTransaction();
}

_assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
  getTransactionWrappers: function () {
    returnTRANSACTION_WRAPPERS; }});Copy the code

Reset when close, set isBatchingUpdates to false.



Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
  getTransactionWrappers: function() {
    returnTRANSACTION_WRAPPERS; }}); var transaction = new ReactDefaultBatchingStrategyTransaction();Copy the code

With a prototype merge, the close method of the transaction will reset isBatchingUpdates after enqueueUpdate, and then initiate a batch update of the DOM


At this point, we can see that all of the previous queues, batchupdates, etc., were just batch collection work to get to this stage of the transaction, and this is where the batchUpdate operation is actually completed.


7. Back to the original title

add() {
    this.setState({
      count: this.state.count + 1
    });
    this.setState({
      count: this.state.count + 1
    });
  }
Copy the code

setTimeout(() => {
  this.setState({
    count: this.state.count + 1
  });
  this.setState({
    count: this.state.count + 1
  });
}, 0);
Copy the code


In the first case, when the first setState is executed, it is already in the big transaction triggered by a click event, and a batchedUpdates has been triggered. IsBatchingUpdates are true, so both setStates will be batch updated. In this case, it is an asynchronous process. This. state does not change immediately. SetState simply passes the partialState into the dirtyComponents and then flushBatchedUpdates in the close phase of the transaction to re-render.


In the second case, with setTimeout, both setStates will be executed after the batch update batchedUpdates in the big transaction triggered by the click event has ended, so they will trigger two batch update batchedUpdates, The two transactions and the function flushBatchedUpdates are executed to synchronize updates.


The latter

Thank you for your patience to see here, hope to gain!

If you are not very busy, please click star⭐ [Github blog portal], which is a great encouragement to the author.

I like to keep records during the learning process and share my accumulation and thinking on the road of front-end. I hope to communicate and make progress with you. For more articles, please refer to [amandakelake’s Github blog].