Является ли setState действительно асинхронным в React?

база данных внешний интерфейс JavaScript React.js

В процессе изучения react почти во всех учебных материалах неоднократно будет подчеркиваться, что setState является асинхронным, давайте взглянем на описание setState на официальном сайте react.

БудуsetState()Думайте об этом как о запросе, а не о немедленном выполнении команды для обновления компонента. Для более впечатляющей производительности React может отложить это и обновить эти компоненты за один раз позже. React не гарантирует, что результат изменения будет доступен сразу после setState.

Очень классический пример выглядит следующим образом.

// state.count 当前为 0
componentDidMount(){
    this.setState({count: state.count + 1});
    console.log(this.state.count)
}

Если вы знакомы с реагированием, вы должны знать, что конечный результат равен 0, а не 1.

Но так ли это на самом деле?

Давайте посмотрим на другой пример

class Hello extends Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
  }
  render() {
    return <div onClick={this.onClick.bind(this)}>点我</div>;
  }

  componentDidMount() {
    //手动绑定mousedown事件
    ReactDom.findDOMNode(this).addEventListener(
      "mousedown",
      this.onClick.bind(this)
    );

    //延时调用onclick事件
    setTimeout(this.onClick.bind(this), 1000);
  }
  onClick(event) {
    if (event) {
      console.log(event.type);
    } else {
      console.log("timeout");
    }
    console.log("prev state:", this.state.counter);
    this.setState({
      counter: this.state.counter + 1
    });
    console.log("next state:", this.state.counter);
  }
}
export default Hello;

Используйте 3 метода для обновления состояния в этом компоненте

  • Привязать событие onClick в узле div
  • Вручную привязать событие mousedown в componentDidMount
  • вызов onClick с setTimeout в компонентеDidMount

Можете ли вы угадать результат после нажатия на компонент? Результат:

timeout
"prev state:"
0
"next state:"
1
mousedown
"prev state:"
1
"next state:"
2
click
"prev state:"
2
"next state:"
2

codesandbox.

Результаты кажутся немного неожиданными, только три способа события onClick, привязанного к div, выводят результат, который доказывает, что setState является асинхронным, два других способа показывают, что setState выглядит синхронным.

Основной участник React Дэн Абрамов также упомянул в ответе

Что, черт возьми, здесь происходит?

Нечего сказать, перейдите непосредственно к исходному коду. Если у вас есть определенное понимание исходного кода реакции, вы можете продолжить его чтение. Если нет, вы можете сразу перейти к заключению (следующий анализ основан на реакции 15, а 16 версия может быть другой).

Асинхронная реализация setState

вызов setState в componentWillMount
//代码位于ReactBaseClasses
 * @param {partialState} 设置的state参数
 * @param {callback} 设置state后的回调
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.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

существуетsetStateназывается вenqueueSetStateМетод помещает входящее состояние в очередь, далее смотритеenqueueSetStateКонкретная реализация:

//代码位于ReactUpdateQueue.js
   * @param {publicInstance} 需要重新渲染的组件实例
   * @param {partialState} 设置的state
   * @internal
  enqueueSetState: function(publicInstance, partialState) {
      //省略部分代码

      //从组件列表中找到并返回需渲染的组件
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'setState',
    );

    if (!internalInstance) {
      return;
    }

    //state队列
    var queue =
      internalInstance._pendingStateQueue ||
      (internalInstance._pendingStateQueue = []);
    //将新的state放入队列
    queue.push(partialState);

    enqueueUpdate(internalInstance);
  },

существуетenqueueSetStateПервый — найти компонент для рендеринга и включить новое состояние в очередь состояний компонента для обновления, а затем вызватьenqueueUpdateметод, затем посмотрите на:

//代码位于ReactUpdateQueue.js
function enqueueUpdate(internalInstance) {
  ReactUpdates.enqueueUpdate(internalInstance);
}
//代码位于ReactUpdates.js
function enqueueUpdate(component) {
  ensureInjected();

  // Various parts of our code (such as ReactCompositeComponent's
  // _renderValidatedComponent) assume that calls to render aren't nested;
  // verify that that's the case. (This is called by each top-level update
  // function, like setState, forceUpdate, etc.; creation and
  // destruction of top-level components is guarded in ReactMount.)

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

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

Этот код является реализациейsetStateКлючом к асинхронному обновлению является batchingStrategy. Как следует из названия, это стратегия пакетного обновления, в которой состояние обновляется пакетами посредством транзакций. Концепции транзакций здесь аналогичны концепциям в базе данных, но они не совсем То же самое Конкретное расширение также является очень интересным контентом в реакции. isBatchingUpdates — это флаг транзакции. Если он равен true, это означает, что React находится в потоке транзакций обновления компонентов. Согласно приведенной выше логике кода:

  • Если его нет в потоке транзакций, вызовите метод batchedUpdates, чтобы войти в процесс обновления.После входа в процесс для isBatchingUpdates будет установлено значение true.
  • В противном случае компоненты, которые нужно обновить, положить в dirtyComponents, это тоже очень понятно, сначала сохранить компоненты, которые нужно обновить, а потом обновить. Это объясняет, что вызов setState в componentDidMount не приведет к немедленному обновлению состояния, потому что оно находится в процессе обновления, а isBatchingUpdates имеет значение true, поэтому оно будет помещено только в dirtyComponents и будет ждать более позднего обновления.
Вызов setState в событии

затем позвоните в событиеsetStateА почему он асинхронный?React реализует привязку событий путем синтеза событий.В входных методах mountComponent и updateComponent создания и обновления компонента привязанные события регистрируются на узле документа, а соответствующая callback-функция передается через хранилище EventPluginHub. Когда событие инициируется, будет вызван обратный вызов, зарегистрированный addEventListener в документе.Функция обратного вызова — ReactEventListener.dispatchEvent, которая является методом входа для распределения события. Давайте посмотрим на dispatchEvent:

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

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

Я видел знакомый метод batchedUpdates, но вызывающий изменил его на ReactUpdates, а затем ввел ReactUpdates.batchedUpdates.

function batchedUpdates(callback, a, b, c, d, e) {
  ensureInjected();
  return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}

Внезапно оказывается, что обработка события также завершается через эту же транзакцию.После входа в процесс обработки события isBatchingUpdates транзакции имеет значение true.Если в событии вызывается метод setState, то он также войдет в процесс dirtyComponent, который является так называемым асинхронным.

Собственная привязка событий и setState в setTimeout

Оглядываясь назад на ситуацию с синхронизацией, привязка собственного события не будет обрабатываться синтетическими событиями и, естественно, не войдет в поток обработки транзакции обновления. То же самое верно и для setTimeout.Когда выполняется обратный вызов setTimeout, исходный процесс обновления компонента завершен, и он не будет помещен в dirtyComponent для асинхронного обновления.Результат, естественно, синхронный.

Кстати, при обновлении композиции слияние обновленного состояния с исходным состоянием происходит после componentWillUpdate и до рендера, поэтому ставится перед componentWillUpdatesetStateВы можете получить последнее значение в рендере.

Суммировать

1. В жизненном цикле компонента или привязке события реакции setState обновляется асинхронно. 2. Вызов setState в отложенном обратном вызове или собственном обратном вызове, привязанном к событию, не обязательно является асинхронным.

Этот результат не означает, что утверждение о том, что setState выполняется асинхронно, неверно. Более точным утверждением должно быть то, что setState не может гарантировать синхронное выполнение.

Дэн Абрамов также много раз упоминал, что setState в будущем будет полностью преобразован в асинхронный, что также подтверждается новой приостановкой, упомянутой в js conf.