技术/前端/React

面试中经常碰到的一个问题就是setState之后发生了什么?这个看似简单,其实可以挖的很深,看下面:

setState两个基本特性

1 setState是异步的,react通常会集齐一些组件后一起更新组件,以保证性能。所以当我们在设置setState后,立即通过this.state是获取最新状态是获取不到的。如果要获取最新的状态可以在setState回调中获取。

  1. this.setState({name: 'laodao'},()=>{})

2 setState进行多次执行,在react执行合并多次为一次情况下,相当于后面覆盖前面,相当于加一次。

Object.assign(
  previousState,
  {quantity: this.state.quantity + 1},
  {quantity: this.state.quantity + 1}
)

setState执行过程

setState 在 ReactBaseClasses.js里面

ReactComponent.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  // 这里的this.updater就是ReactUpdateQueue
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

setState函数有两个参数:

第一个参数是需要修改的setState对象,或者是函数。

第二个参数是修改之后的回调函数。

调用 enqueueSetState

enqueueSetState: function (publicInstance, partialState) {
     // 获取当前组件的instance
    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');

     // 将要更新的state放入一个数组里
     // _pendingStateQueue(待更新队列) 与 _pendingCallbacks(更新回调队列)
     var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
    queue.push(partialState);

     //  将要更新的component instance也放在一个队列里
    enqueueUpdate(internalInstance);
  }

enqueueSetState 主要任务: 1、将新的state放进数组里 2、用enqueueUpdate来处理将要更新的实例对象

enqueueUpdate

function enqueueUpdate(component) {
  // 如果没有处于批量创建/更新组件的阶段,则处理update state事务
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }
  // 如果正处于批量创建/更新组件的过程,将当前的组件放在dirtyComponents数组中
  dirtyComponents.push(component);
}

由这段代码可以看到,当前如果正处于创建/更新组件的过程,就不会立刻去更新组件,而是先把当前的组件放在dirtyComponent里,所以不是每一次的setState都会更新组件~。

这段代码就解释了我们常常听说的: setState是一个异步的过程,它会集齐一批需要更新的组件然后一起更新

而batchingStrategy 又是个什么东西呢?

batchingStrategy

var ReactDefaultBatchingStrategy = {
  // 用于标记当前是否出于批量更新
  isBatchingUpdates: false,
  // 当调用这个方法时,正式开始批量更新
  batchedUpdates: function (callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

    ReactDefaultBatchingStrategy.isBatchingUpdates = true;

    // 如果当前事务正在更新过程在中,则调用callback,既enqueueUpdate
    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
    // 否则执行更新事务
      return transaction.perform(callback, null, a, b, c, d, e);
    }
  }
};

这里注意两点: 1、如果当前事务正在更新过程中,则使用enqueueUpdate将当前组件放在dirtyComponent里。 2、如果当前不在更新过程的话,则执行更新事务。

transaction

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

简单说明一下transaction对象,它暴露了一个perform的方法,用来执行anyMethod,在anyMethod执行的前,需要先执行所有wrapper的initialize方法,在执行完后,要执行所有wrapper的close方法,就辣么简单。

在ReactDefaultBatchingStrategy.js,tranction 的 wrapper有两个 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];

这两个wrapper的initialize都没有做什么事情,但是在callback执行完之后,RESET_BATCHED_UPDATES 的作用是将isBatchingUpdates置为false, FLUSH_BATCHED_UPDATES 的作用是执行flushBatchedUpdates,然后里面会循环所有dirtyComponent,调用updateComponent来执行所有的生命周期方法,componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, render, componentDidUpdate 最后实现组件的更新。

[image:00C17BF8-01AE-4394-B9D1-8A7EC8BA769C-488-0000B560DE0CA5EF/v2-fc21554110ae7147a5e01c7db6f3cf4f_b.jpg]

[image:0F6EE429-D91F-4A9F-B57B-662903A52041-488-0000B560DDE95C24/v2-fc21554110ae7147a5e01c7db6f3cf4f_hd.jpg]

setState的异步特性

首先setState只有在生命周期和合成事件回调中才会有所谓的异步效果,才会进入上面说的批量更新的逻辑中:

dispatchEvent: **function** (topLevelType, nativeEvent) {
    // disable了则直接不回调相关方法
    **if** (!ReactEventListener._enabled) {
      **return**;
    }

    **var** bookKeeping = TopLevelCallbackBookKeeping.getPooled(topLevelType, nativeEvent);
    **try** {
      // 放入
      ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
    } **finally** {
      TopLevelCallbackBookKeeping.release(bookKeeping);
    }
}

而在原生事件中或定时任务里,由于没有触发事务操作即ReactDefaultBatchingStrategy中的batchUpdates方法,无法修改isBatchingUpdates变量,所以会直接修改state,以及触发组件更新