Does React keep the order for state updates? Ask Question

Does React keep the order for state updates? Ask Question

I know that React may perform state updates asynchronously and in batch for performance optimization. Therefore you can never trust the state to be updated after having called setState. But can you trust React to update the state in the same order as setState is called for

  1. the same component?
  2. different components?

Consider clicking the button in the following examples:

1. Is there ever a possibility that a is false and b is true for:

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false, b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.setState({ a: true });
    this.setState({ b: true });
  }
}

2. Is there ever a possibility that a is false and b is true for:

class SuperContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false };
  }

  render() {
    return <Container setParentState={this.setState.bind(this)}/>
  }
}

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.props.setParentState({ a: true });
    this.setState({ b: true });
  }
}

Keep in mind that these are extreme simplifications of my use case. I realize that I can do this differently, e.g. updating both state params at the same time in example 1, as well as performing the second state update in a callback to the first state update in example 2. However, this is not my question, and I am only interested in if there is a well defined way that React performs these state updates, nothing else.

Any answer backed up by documentation is greatly appreciated.

ベストアンサー1

I work on React.

TLDR:

But can you trust React to update the state in the same order as setState is called for

  • the same component?

Yes.

  • different components?

Yes.

The order of updates is always respected. Whether you see an intermediate state "between" them or not depends on whether you're inside in a batch or not.

In React 17 and earlier, only updates inside React event handlers are batched by default. There is an unstable API to force batching outside of event handlers for rare cases when you need it.

Starting from React 18, React batches all updates by default.Reactは2つの異なる意図的なイベント(クリックや入力など)からの更新をバッチ処理することはないので、例えば2つの異なるボタンクリックはバッチ処理されません。バッチ処理が望ましくないまれなケースでは、flushSync


これを理解する鍵はsetState()いくつのコンポーネントをいくつ呼び出してもReactイベントハンドラ内イベント終了時に1回だけ再レンダリングを行う。Childこれは、クリック イベントを処理するときにとParent各 を呼び出すときに を2 回setState()再レンダリングしたくないため、大規模なアプリケーションで良好なパフォーマンスを得るには非常に重要です。Child

どちらの例でも、setState()呼び出しは React イベント ハンドラー内で行われます。したがって、それらは常にイベントの終了時に一緒にフラッシュされます (中間状態は表示されません)。

アップデートは常に発生順に浅く結合されるしたがって、最初の更新が{a: 10}、2 番目が{b: 20}、3 番目が の場合{a: 30}、レンダリングされた状態は になります{a: 30, b: 20}。同じ状態キーに対するより最近の更新 (たとえば、a私の例のように) が常に「勝ちます」。

オブジェクトthis.stateは、バッチの最後にUIを再レンダリングするときに更新されます。したがって、以前の状態に基づいて状態を更新する必要がある場合(カウンターを増やすなど)、setState(fn)から読み取るのではなく、以前の状態を提供する機能バージョンを使用する必要がありますthis.state。この理由について興味がある場合は、詳しく説明しました。このコメントで


あなたの例では、「中間状態」は見られません。Reactイベントハンドラ内バッチ処理が有効になっている場合(React はイベントを終了するタイミングを「認識」しているため)。

しかし、React 17とそれ以前のバージョンでは、Reactイベントハンドラ以外ではデフォルトでバッチ処理は行われなかった. したがって、あなたの例で の代わりに AJAX レスポンス ハンドラを使用するとhandleClick、それぞれはsetState()発生した時点ですぐに処理されます。この場合、だろうReact 17 以前では中間状態が表示されます。

promise.then(() => {
  // We're not in an event handler, so these are flushed separately.
  this.setState({a: true}); // Re-renders with {a: true, b: false }
  this.setState({b: true}); // Re-renders with {a: true, b: true }
  this.props.setParentState(); // Re-renders the parent
});

不便だとは思いますがイベントハンドラ内かどうかによって動作が異なりますReact 18ではこれは不要になりましたが、それ以前は、バッチ処理を強制するために使用できるAPIがありました:

promise.then(() => {
  // Forces batching
  ReactDOM.unstable_batchedUpdates(() => {
    this.setState({a: true}); // Doesn't re-render yet
    this.setState({b: true}); // Doesn't re-render yet
    this.props.setParentState(); // Doesn't re-render yet
  });
  // When we exit unstable_batchedUpdates, re-renders once
});

内部的に React イベント ハンドラーはすべて でラップされているunstable_batchedUpdatesため、デフォルトでバッチ処理されます。 更新を 2 回ラップしても効果がないことに注意してくださいunstable_batchedUpdates。更新は、最も外側の呼び出しを終了するときにフラッシュされますunstable_batchedUpdates

この API は、18 以降のメジャー バージョン (19 以降) で最終的に削除されるという意味で「不安定」です。React イベント ハンドラーの外部でバッチ処理を強制する必要がある場合は、React 18 まではこの API を安全に使用できます。React 18 では、この API は効果がなくなるため、削除できます。


まとめると、Reactはデフォルトでイベントハンドラ内でのみバッチ処理を行っていたため、これは混乱を招くトピックです。しかし、解決策はバッチレス、それはさらにバッチ処理デフォルトではこれです。React 18 ではこれを行っています。

おすすめ記事