1. ホーム
  2. javascript

[解決済み] Reactは状態の更新の順番を守るのか?

2022-04-22 16:50:29

質問

Reactはパフォーマンスの最適化のために、状態の更新を非同期かつ一括で行うことがありますね。そのため setState . しかし、あなたはReactを信頼して と同じ順序で状態を更新します。 setState が呼び出されます。 に対して

  1. は、同じコンポーネントですか?
  2. 異なるコンポーネント?

以下の例でボタンをクリックすることを考えます。

1. という可能性はないのでしょうか? aは偽であり、bは真である のためのものです。

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. という可能性はないのでしょうか? aは偽であり、bは真である のためのものです。

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

これらは私のユースケースを極端に単純化したものであることに留意してください。例えば、例 1 では両方の状態を同時に更新し、例 2 では最初の状態の更新に対するコールバックで 2 番目の状態の更新を行うなど、別の方法も可能だと思います。しかし、これは私の質問ではなく、私はReactがこれらの状態の更新を実行する明確に定義された方法があるかどうかに興味があるだけで、それ以外のことはありません。

ドキュメントに裏打ちされた回答があれば、非常にありがたいです。

解決方法を教えてください。

私はReactで仕事をしています。

TLDR

しかし、ReactがsetStateが呼ばれたのと同じ順序で状態を更新することを信頼できますか?

  • 同じコンポーネントですか?

はい。

  • 異なるコンポーネント?

はい。

その オーダー の更新は常に尊重されます。中間状態を見るかどうかは、バッチ内部かどうかに依存します。

現在(React 16以前)。 Reactのイベントハンドラ内の更新のみがデフォルトでバッチ処理されます。 . イベントハンドラの外側で強制的にバッチ処理を行うための不安定なAPIがあるので、それが必要な場合は稀に利用することができます。

将来のバージョン(おそらくReact 17以降)では、Reactはデフォルトですべての更新をバッチ処理するので、このことについて考える必要はありません。この点に関する変更は、いつものように Reactブログ とリリースノートに記載しています。


これを理解するためのポイントは いくら setState() の呼び出しは、どれだけ多くのコンポーネントで Reactのイベントハンドラ内 の場合、イベント終了時に1回だけ再レンダリングが行われます。 . これは大規模なアプリケーションで良好なパフォーマンスを発揮するために重要です。 ChildParent 各呼び出し setState() を再描画したくない場合は、クリックイベントを処理する際に Child を2回実行します。

どちらの例でも setState() の呼び出しは、Reactのイベントハンドラ内で発生します。したがって、それらは常にイベントの終了時に一緒にフラッシュされます(そして、あなたは中間状態を見ることはありません)。

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

その this.state オブジェクトは、バッチの終了時にUIを再レンダリングする際に更新されます。そのため、以前の状態に基づいて状態を更新する必要がある場合(カウンターのインクリメントなど)には、機能的な setState(fn) バージョンから読み込むのではなく、前の状態を得ることができます。 this.state . この理由が気になる方は、詳しく説明した このコメントで .


この例では、次のような理由で中間状態を見ることはできません。 Reactのイベントハンドラ内 バッチ処理が有効な場合(Reactはいつそのイベントを終了するのかを知っているため)。

ただし、React16でもそれ以前のバージョンでも。 Reactのイベントハンドラ以外では、まだデフォルトでバッチ処理は行われていません。 . したがって、あなたの例では、AJAXレスポンスハンドラの代わりに handleClick というように、それぞれの setState() が発生すると、即座に処理されます。この場合、そう、あなたは となります。 は中間状態を見ることができます。

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バージョンで変更され、デフォルトですべての更新をバッチ処理するようになります(そして、変更を同期的にフラッシュするためのオプトインAPIを提供します)。デフォルトの挙動を切り替えるまでは(潜在的にはReact 17で)。 強制的にバッチ処理を行うために使用できる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 そのため、デフォルトでバッチ処理されます。更新を unstable_batchedUpdates を2回使っても何の効果もありません。更新は、一番外側の unstable_batchedUpdates を呼び出します。

このAPIはquot;unstable"で、すでにデフォルトでバッチングが有効になっている場合は削除される予定です。しかし、マイナーバージョンでは削除されないので、Reactのイベントハンドラ以外のケースでバッチ処理を強制する必要がある場合は、React 17まで安全に頼ることができます。


結論から言うと、Reactはデフォルトではイベントハンドラ内でのみバッチ処理を行うため、このトピックは分かりにくいです。これは将来のバージョンで変更され、その時の動作はより分かりやすくなるでしょう。しかし、解決策としては バッチを減らす それは バッチリ をデフォルトで使用します。それが、私たちがやろうとしていることです。