web-dev-qa-db-ja.com

Reactは状態更新の順序を守りますか?

私はReactがパフォーマンスの最適化のために非同期にそしてバッチで状態更新を行うかもしれないことを知っています。したがって、setStateを呼び出した後に状態が更新されることを信頼することはできません。しかしsetStateが呼ばれるのと同じ順序で状態を更新するためにReactを信頼できますか?

  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がこれらの状態更新を実行する明確に定義された方法です。

ドキュメンテーションによって裏付けられた答えは大歓迎です。

96
darksmurf

私はReactに取り組んでいます。

TLDR:

ただし、Reactを信頼して、setStateが呼び出されるのと同じ順序で状態を更新できますか

  • 同じコンポーネント?

はい。

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

はい。

orderの更新は常に尊重されます。それらの間の中間状態を見るかどうかは、バッチの中にいるかどうかによって決まります。

現在(React 16以前)、Reactイベントハンドラー内の更新のみが既定でバッチ処理されます。まれに、必要なときにイベントハンドラの外部で強制的にバッチ処理を行う不安定なAPIがあります。

将来のバージョン(おそらくReact 17以降)では、Reactがデフォルトですべての更新をバッチ処理するため、これについて考慮する必要はありません。いつものように、これに関する変更は React blog およびリリースノートで発表します。


これを理解するための鍵は、Reactイベントハンドラー内で実行するコンポーネントの数setState()が何回呼び出されても 、イベントの終了時に1つの再レンダリングのみを生成しますChildおよびParentがそれぞれクリックイベントを処理するときにsetState()を呼び出す場合、Childを再レンダリングしたくないため、これは大規模なアプリケーションで良好なパフォーマンスを得るために重要です。二回。

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

更新は常に発生順に浅くマージされます。したがって、最初の更新が{a: 10}、2番目が{b: 20}、3番目が{a: 30}の場合、レンダリングされた状態は{a: 30, b: 20}になります。同じ状態キーに対する最新の更新(例ではaなど)は常に「勝ちます」。

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


あなたの例では、Reactイベントハンドラー内でバッチ処理が有効になっているため、「中間状態」は表示されません(React " 「そのイベントを終了するとき)。

ただし、React 16およびそれ以前のバージョンの両方で、Reactイベントハンドラー以外のデフォルトではまだバッチ処理はありません。したがって、あなたの例でhandleClickの代わりにAJAX応答ハンドラーがある場合、各setState()は発生時にすぐに処理されます。この場合、はい、あなたはwould中間状態を見ます:

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は、バッチ処理がデフォルトですでに有効になっている場合に削除するという意味で「不安定」です。ただし、マイナーバージョンでは削除しないため、Reactイベントハンドラーの外部でバッチ処理を強制する必要がある場合は、React 17まで安全に信頼できます。


要約すると、Reactはデフォルトでイベントハンドラー内でのみバッチ処理を行うため、これは紛らわしいトピックです。これは将来のバージョンで変更され、動作はより簡単になります。しかし、解決策はbatch lessではなく、デフォルトではbatch moreです。それが私たちがやろうとしていることです。

273
Dan Abramov

doc のように

setState()はコンポーネント状態への変更をエンキューして、このコンポーネントとその子を更新された状態で再レンダリングする必要があることをReactに伝えます。これは、イベントハンドラとサーバーの応答に応じてユーザーインターフェイスを更新するために使用する主な方法です。

それはキューのように変更を実行します(FIFO:先入れ先出し)最初の呼び出しが先に実行されます

3
Ali

これは実際には非常に興味深い質問ですが、答えは複雑すぎてはいけません。この素晴らしい 中規模の記事 があり、そのような質問に答えています。

1)これをしたら

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

batching のため、atruebfalseになることはないと思います。

ただし、baに依存している場合、実際には期待した状態にならない可能性があります。

// assuming this.state = { value: 0 };
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});

上記のすべての呼び出しが処理された後、this.state.valueは1になります。予想したように3にはなりません。

これは記事で言及されています:setState accepts a function as its parameter

// assuming this.state = { value: 0 };
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));

これは私たちにthis.state.value === 3を与えるでしょう

3
Michal

同じサイクル中の複数の呼び出しはまとめてバッチ処理することができます。たとえば、同じサイクルで品目数量を複数回増やそうとすると、次のようになります。

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

https://reactjs.org/docs/react-component.html

2
Mosè Raguzzini