setState

SetState () queues changes to the component state to defer updates in bulk and tells React that it needs to rerender the component and its children with the updated state. In fact, setState is not asynchronous, but the code is executed in a different order, giving it an asynchronous feel.

Method of use setState (stateChange | updater [, callback])

  • StateChange – as the object passed in, it will be superficially merged into the new state
  • updater – (state, props) => stateChange, returns a new object built based on state and props, which will be superficially merged into the new state
  • Callback – An optional callback function

After using setState() to change the state, the latest state is not immediately available through this.state

You can use the callback function in componentDidUpdate() or setState(updater, callback) to ensure that it is triggered after an update is applied. It is usually recommended to use componentDidUpdate()

The effects of multiple setState() calls are merged

React batches multiple setStates () in the same cycle for better sense performance. Trigger backflow by triggering a component update. SetState () called later overrides the value of setState() called first in the same cycle. So if the next state depends on the previous state, it is recommended to pass function to setState()

onClick = () => { this.setState({ quantity: this.state.quantity + 1 }); this.setState({ quantity: this.state.quantity + 1 }); } // React this method will eventually become object. assign(previousState, {quantity: state.quantity + 1}, {quantity: state.quantity + 1}, ... )Copy the code

Synchronous asynchronous update |

  • Synchronous update
    • React event handling (e.g. OnClick)
    • React Life cycle function
  • Asynchronous update
    • React bypasses the React event handler added directly via addEventListener
    • Through the setTimeout | | setInterval asynchronous calls

After setState() is called, the source code executes the stack

React See version 15.6.0

1. setState()

Source code paths 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

2. enqueueSetState(); enqueueCallback()

Source code paths SRC/renderers/Shared/stack/reconciler/ReactUpdateQueue. Js

This file exports a ReactUpdateQueue object (React update queue)

enqueueSetState: function(publicInstance, partialState) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'setState',);if(! internalInstance) {return;
    }
    var queue =
      internalInstance._pendingStateQueue ||
      (internalInstance._pendingStateQueue = []);
    queue.push(partialState);
    enqueueUpdate(internalInstance);
}
enqueueCallback: function(publicInstance, callback, callerName) {
    ReactUpdateQueue.validateCallback(callback, callerName);
    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
    if(! internalInstance) {return null;
    }
    if (internalInstance._pendingCallbacks) {
      internalInstance._pendingCallbacks.push(callback);
    } else {
      internalInstance._pendingCallbacks = [callback];
    }
    enqueueUpdate(internalInstance);
  }
Copy the code

3. enqueueUpdate()

Source code paths SRC/renderers/Shared/stack/reconciler/ReactUpdates. Js

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

If you are not in batch update mode, execute the batchedUpdates method on all the updates in the queue.

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

4. batchedUpdates()

Source code paths SRC/renderers/Shared/stack/reconciler/ReactDefaultBatchingStrategy. Js

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

var transaction = new ReactDefaultBatchingStrategyTransaction();
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

5. transaction initialize and close

Source code paths SRC/renderers/Shared/stack/reconciler/ReactDefaultBatchingStrategy. Js

There are two wrappers registered in Transaction, RESET_BATCHED_UPDATES and FLUSH_BATCHED_UPDATES.

In the Initialize phase, both wrappers are empty methods that do nothing.

In the close phase, RESET_BATCHED_UPDATES set isBatchingUpdates to false; FLUSH_BATCHED_UPDATES Run the flushBatchedUpdates command to execute update.

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

6. Render updates

ReactUpdates.flushBatchedUpdates() - ReactUpdates.runBatchedUpdates() - ReactCompositeComponent.performUpdateIfNecessary() - receiveComponent() + updateComponent()

  1. The runBatchedUpdates loop iterates through the dirtyComponents array and does two things:

    • First execute performUpdateIfNecessary to refresh the component’s view
    • Execute the previously blocked callback
  2. ReceiveComponent will finally call updateComponent

  3. UpdateComponent will execute the React components exist in period of life cycle method, such as componentWillReceiveProps shouldComponentUpdate, componentWillUpdate, Render, componentDidUpdate. Thus complete the whole process of component update

  4. Before shouldComponentUpdate, the _processPendingState method is executed, which handles state:

    • If the update queue is null, the original state is returned;
    • If the update queue has an update, return the update value;
    • If the update queue has multiple updates, they are combined through the for loop;
  5. In a lifetime, before componentShouldUpdate is executed, all state changes are merged and consolidated.

flushBatchedUpdates(); RunBatchedUpdates () source paths SRC/renderers/Shared/stack/reconciler/ReactUpdates. Js

var flushBatchedUpdates = function() {
  while (dirtyComponents.length || asapEnqueued) {
    if (dirtyComponents.length) {
      var transaction = ReactUpdatesFlushTransaction.getPooled();
      transaction.perform(runBatchedUpdates, null, transaction);
      ReactUpdatesFlushTransaction.release(transaction);
    }

    if (asapEnqueued) {
      asapEnqueued = false; var queue = asapCallbackQueue; asapCallbackQueue = CallbackQueue.getPooled(); queue.notifyAll(); CallbackQueue.release(queue); }}};function runBatchedUpdates(transaction) {
  var len = transaction.dirtyComponentsLength;
  dirtyComponents.sort(mountOrderComparator);
  updateBatchNumber++;

  for(var i = 0; i < len; I ++) {var Component = component [I]; Callback var callbacks = component._pendingCallbacks; component._pendingCallbacks = null; var markerName;if (ReactFeatureFlags.logTopLevelRenders) {
      var namedComponent = component;
      if (component._currentElement.type.isReactTopLevelWrapper) {
        namedComponent = component._renderedComponent;
      }
      markerName = 'React update: '+ namedComponent.getName(); console.time(markerName); } / / execution updateComponent ReactReconciler. PerformUpdateIfNecessary (component, transaction reconcileTransaction, updateBatchNumber, ); // Execute previously unexecuted callback in dirtyComponentif (callbacks) {
      for(var j = 0; j < callbacks.length; j++) { transaction.callbackQueue.enqueue( callbacks[j], component.getPublicInstance(), ); }}}}Copy the code

PerformUpdateIfNecessary () source paths SRC/renderers/Shared/stack/reconciler/ReactReconciler. Js

performUpdateIfNecessary: function( internalInstance, transaction, updateBatchNumber, ) { internalInstance.performUpdateIfNecessary(transaction); }};Copy the code

PerformUpdateIfNecessary () source paths SRC/renderers/Shared/stack/reconciler/ReactCompositeComponent. Js

  performUpdateIfNecessary: function(transaction) {
    if(this._pendingElement ! = null) { ReactReconciler.receiveComponent( this, this._pendingElement, transaction, this._context, ); }else if(this._pendingStateQueue ! == null || this._pendingForceUpdate) { this.updateComponent( transaction, this._currentElement, this._currentElement, this._context, this._context, ); }else{ this._updateBatchNumber = null; }},Copy the code

UpdateComponent () source paths SRC/renderers/Shared/stack/reconciler/ReactCompositeComponent. Js

The transaction concept

A Transaction is simply a Transaction that wraps anyMethod that needs to be executed in a wrapper and executes it using the perform method provided by Transaction. Execute the Initialize method in all wrappers and the close method in all Wrappers after perform. A set of initialize and close methods is called a wrapper, and Transaction supports multiple wrapper stacks.

Transaction in React provides a Mixin for other modules to implement their own transactions. To implement your own Transaction, you need to implement an additional abstract getTransactionWrappers() interface, which is the initialize and close methods that Transaction uses to get all wrappers, so you need to return an array object, Each object has methods with keys initialize and close, respectively.

var Transaction = require('Transaction');
var emptyFunction = require('emptyFunction');

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();
}

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

SetState process

The setState process is complex and cleverly designed to avoid redundant and unnecessary refreshing of components. React makes extensive use of the injection mechanism so that the same instantiated object is injected each time, preventing multiple instantiations

  1. EnqueueSetState queues the state and calls enqueueUpdate to process the Component to update
  2. If the Component is currently in an Update transaction, store the Component in the dirtyComponent first. Otherwise, the batchedUpdates handler is called
  3. BatchedUpdates initiates a transaction.perform() transaction
  4. Transaction initialization starts, runs, and finishes
    • Initialization: There are no methods registered during transaction initialization, so there are no methods to execute
    • Run: The callback method passed in when setSate is executed, usually without passing a callback argument
    • End: ExecuteRESET_BATCHED_UPDATES FLUSH_BATCHED_UPDATESThe close method in both wrappers
  5. FLUSH_BATCHED_UPDATESIn the close phase, the flushBatchedUpdates method loops through all dirtyComponents, calling updateComponent to refresh the component, And execute its pendingCallbacks, the callback set in setState

After a component is mounted, setState is typically triggered by a DOM interaction event, such as Click

  1. Click the button
  2. ReactEventListener triggers the dispatchEvent method
  3. DispatchEvent call ReactUpdates batchedUpdates
  4. Enter transaction, init is null, anyMethod isReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
    • HandleTopLevelImpl is the callback method that calls the DOM event here
    • And then thesetState()
      • Place the state change and the corresponding callback function in the _pendingStateQueue and _pendingCallback
      • Place the components that need to be updated in the dirtyComponents sequence
    • Perform the perform ()
    • Perform a close render update
dispatchEvent: function(topLevelType, nativeEvent) {
    if(! ReactEventListener._enabled) {return;
    }

    var bookKeeping = TopLevelCallbackBookKeeping.getPooled(
      topLevelType,
      nativeEvent,
    );
    try {
      // Event queue being processed inthe same cycle allows // `preventDefault`. ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping); } finally { TopLevelCallbackBookKeeping.release(bookKeeping); }}Copy the code