Reactは状態更新の順序を保持しますか?


139

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

  1. 同じコンポーネント?
  2. 異なるコンポーネント?

次の例のボタンをクリックすることを検討してください。

1. aがfalse、bがtrueである可能性はありますか?

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がfalseでbがtrueである可能性はありますか?

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がこれらの状態更新を実行する明確に定義された方法がある場合にのみ興味があります。

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


1
これを参照してください:stackoverflow.com/a/36087156/3776299
helb

3
それは無意味な質問のようではありません。また、反応ページのgithubの問題でその質問をすることもできます。通常、dan abramovが非常に役に立ちます。私がそのようなトリッキーな質問をしたとき、私は尋ねるでしょう、そして彼は答えました。悪い点は、これらの種類の問題が公式ドキュメントのように公開されないことです(そのため、他の人も簡単にアクセスできます)。私はまた、公式ドキュメントは、あなたの質問からなどのトピックのようないくつかのトピックの広範なカバレッジを欠いリアクト感じる
giorgim

たとえば、github.com / facebook / react / issues / 11793を例にとります。この問題で説明されている内容は多くの開発者に役立つと思いますが、FBの人々は高度なものだと考えているため、公式ドキュメントには記載されていません。同じことがおそらく他のことについてもです。あなたの質問のように国家管理のすべての隅々のケースを探求する「国家管理の詳細な対応」や「国家管理の落とし穴」のようなタイトルの公式記事は悪くないと思います。多分私たちはFB開発者にそのようなものでドキュメントを拡張するようにプッシュすることができます:)
giorgim

Threは、私の質問の中の素晴らしい記事へのリンクです。州のユースケースの95%をカバーする必要があります。:)
ミハル

2
@Michalですが、その記事はまだこの質問に答えていません
IMHO

回答:


335

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

TLDR:

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

  • 同じコンポーネント?

はい。

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

はい。

更新の順序は常に尊重されます。それらの「中間」の中間状態が表示されるかどうかは、バッチ内にいるかどうかによって異なります。

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

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


これを理解するための鍵は、Reactイベントハンドラー内で行うコンポーネントの呼び出し回数に関係なく、イベントの終了時に単一の再レンダリングのみが生成されることsetState()です。場合ので、これは大規模なアプリケーションで優れたパフォーマンスを得るために不可欠であるChildParent、各コール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 16とそれ以前のバージョンのどちらでも、デフォルトでは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
});

イベントハンドラーを使用しているかどうかによって動作が異なるのは不便です。これは、デフォルトですべての更新をバッチ処理する(そして変更を同期的にフラッシュするオプトインAPIを提供する)将来のReactバージョンで変更されます。デフォルトの動作を切り替えるまで(おそらく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_batchedUpdates2回ラップしても効果がないことに注意してください。最も外側のunstable_batchedUpdates呼び出しを終了すると、更新がフラッシュされます。

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


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


1
「常に正しい順序にする」ための 1つの方法は、一時オブジェクトを作成し、さまざまな値(などobj.a = true; obj.b = true)を割り当て、最後にを実行することですthis.setState(obj)。これは、イベントハンドラの内部かどうかに関係なく安全です。イベントハンドラの外で何度も状態を設定するという間違いを犯しがちな場合は、きちんとしたトリックかもしれません。
クリス

そのため、バッチ処理を1つのイベントハンドラーだけに限定することに実際に依存することはできません。少なくとも、これはすぐには当てはまらないためです。次に、setStateをupdater関数と共に使用して、最新の状態にアクセスする必要がありますよね?しかし、state.filterを使用してXHRでデータを読み取り、それらを状態にする必要がある場合はどうでしょうか。延期されたコールバック(したがって副作用)のあるXHRをアップデーターに配置する必要があるようです。それはそれでベストプラクティスと考えられますか?
マクシムグメロフ2018

1
ちなみに、これはthis.stateをまったく読み取らないことも意味します。あるstate.Xを読み取るための唯一の合理的な方法は、その引数からupdater関数で読み取ることです。また、this.stateへの書き込みも安全ではありません。では、なぜthis.stateへのアクセスを許可するのでしょうか。それらはおそらく独立した質問になるはずですが、ほとんどの場合、説明が正しいかどうかを理解しようとしています。
マクシムグメロフ2018

10
この答えはreactjs.orgのドキュメントに追加する必要があります
Deen John

2
「Reactイベントハンドラ」componentDidUpdateにReact 16以降のライフサイクルコールバックが含まれているかどうかをこの記事で説明してください。前もって感謝します!
イワン

6

これは実際には非常に興味深い質問ですが、答えはそれほど複雑であってはなりません。答えがあるメディアに関するこの素晴らしい記事があります。

1)これを行う場合

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

私は状況があるだろうとは思わないaだろうtruebなりますfalseので、のバッチ処理

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

// 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された後、予想される3ではなく1になります。

これは記事で言及されています: 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


どのような場合this.state.value(イベントハンドラの両方で更新されますsetState(ここで、バッチ処理される)と、AJAXコールバックsetStateされていないバッチ処理)。イベントハンドラーでは、updater functionを使用して、関数によって提供される現在更新されている状態を使用して状態を更新します。setStateバッチ処理されていないことがわかっている場合でも、AJAXコールバックのコード内で更新関数を使用する必要がありますか?setStateAJAXコールバック内での更新関数の使用の有無を明確にしていただけませんか?ありがとうございました!
tonix

@Michal、こんにちはMichalは単なる質問をしたかったのですが、this.setState({value:0}); this.setState({value:this.state.value + 1}); 最初のsetStateは無視され、2番目のsetStateのみが実行されますか?
ディケンズ

@ディケンズ私は両方setStateが実行されると信じていますが、最後の方が勝つでしょう。
ミハル

3

同じサイクル中の複数の呼び出しは、一緒にバッチ処理される場合があります。たとえば、同じサイクルでアイテムの数量を複数回インクリメントしようとすると、次のようになります。

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

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


3

以下のようなドキュメント

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

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


こんにちはアリ、ただ質問をしたかっただけですが、this.setState({value:0}); this.setState({value:this.state.value + 1}); 最初のsetStateは無視され、2番目のsetStateのみが実行されますか?
ディケンズ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.