React: RFClarification:なぜ `setState`は非同期なのですか?

作成日 2017ĺš´11月11日  Âˇ  31コメント  Âˇ  ソース: facebook/react

かなり長い間、私はsetStateが非同期である理由を理解しようとしました。 そして、過去にそれに対する答えを見つけることができなかったので、私はそれが歴史的な理由のためであり、おそらく今は変えるのが難しいという結論に達しました。 しかし、 @ gaearonは明確な理由があることを示したので、知りたいと思っています:)

とにかく、よく耳にする理由はここにありますが、簡単に対抗できるので、すべてではないと思います

非同期レンダリングには非同期setStateが必要です

多くの人は当初、それはレンダリング効率のせいであると考えています。 しかし、それがこの動作の背後にある理由ではないと思います。なぜなら、setStateを非同期レンダリングと同期させることは、私には些細なことのように聞こえるからです。

Component.prototype.setState = (nextState) => {
  this.state = nextState
  if (!this.renderScheduled)
     setImmediate(this.forceUpdate)
}

実際、たとえばmobx-react使用すると、オブザーバブルへの同期割り当てが可能になり、レンダリングの非同期性が尊重されます。

どの状態が_レンダリングされた_かを知るには、非同期setStateが必要です。

私が時々耳にする他の議論は、あなたが_要求された_状態ではなく、_レンダリングされた_状態について推論したいということです。 しかし、この原則にも多くのメリットがあるかどうかはわかりません。 概念的にはそれは私には奇妙に感じます。 レンダリングは副作用であり、状態は事実に関するものです。 今日、私は32歳ですが、所有しているコンポーネントが今年再レンダリングできるかどうかに関係なく、来年は33歳になります:)。

(おそらく良くない)平行線を描くには:自分で書いた単語文書の最後のバージョンを印刷するまで_読む_ことができない場合、それはかなり厄介です。 たとえば、ゲームエンジンは、ゲームのどの状態が正確にレンダリングされ、どのフレームがドロップされたかについてのフィードバックを提供するとは思いません。

興味深い観察:2年間でmobx-react誰も私に質問をしませんでした:私の観測量がレンダリングされていることをどうやって知ることができますか? この質問はあまり関連性がないようです。

どのデータがレンダリングされたかを知ることが適切であるといういくつかのケースに遭遇しました。 私が覚えているのは、レイアウトのためにいくつかのデータのピクセル寸法を知る必要がある場合でした。 しかし、それはdidComponentUpdateを使用することでエレガントに解決され、 setStateが非同期であることに実際には依存していませんでした。 これらのケースは非常にまれであるため、主にそれらを中心にAPIを設計することはほとんど正当化されません。 なんとかできれば十分だと思います


ReactチームがsetStateの非同期性がしばしばもたらす混乱を認識していることは間違いないので、現在のセマンティクスには別の非常に正当な理由があると思います。 もっと教えてください:)

Discussion

最も参考になるコメント

だからここにいくつかの考えがあります。 これは決して完全な応答ではありませんが、何も言わないよりも役立つかもしれません。

まず、更新をバッチ処理するために調整を遅らせることが有益であることに同意すると思います。 つまり、 setState()同期的な再レンダリングは多くの場合非効率的であり、複数の更新が行われる可能性が高いことがわかっている場合は、更新をバッチ処理することをお勧めします。

例えば、我々はブラウザ内でならclickハンドラ、および両方のChildとParent呼んsetState我々は再レンダリングする必要はありません、 Child 2回使用し、代わりにそれらをダーティとしてマークし、ブラウザイベントを終了する前にそれらを一緒に再レンダリングすることを好みます。

あなたが求めているのは、なぜ同じこと(バッチ処理)を実行できないのに、調整の終了を待たずにsetState更新をthis.stateすぐに書き込むことができないのかということです。 明白な答えは1つではないと思いますが(どちらのソリューションにもトレードオフがあります)、考えられる理由がいくつかあります。

内部の一貫性を保証する

stateが同期的に更新されても、 propsは更新されません。 (親コンポーネントを再レンダリングするまでprops知ることはできません。これを同期的に行うと、バッチ処理はウィンドウの外に出ます。)

現在、Reactによって提供されるオブジェクト( state 、 props 、 refs )は、内部的に相互に

状態だけを使用する場合、(提案したように)

console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 2

ただし、この状態を解除していくつかのコンポーネントで共有する必要があるため、親に移動するとします。

-this.setState({ value: this.state.value + 1 });
+this.props.onIncrement(); // Does the same thing in a parent

setState()依存する典型的なReactアプリでは、これがます。

ただし、これはコードを壊します!

console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0

これは、提案したモデルでは、 this.stateはすぐにフラッシュされますが、 this.propsはフラッシュされないためです。 また、親を再レンダリングせずにthis.propsをすぐにフラッシュすることはできません。つまり、バッチ処理をあきらめる必要があります(場合によっては、パフォーマンスが大幅に低下する可能性があります)。

props (まだフラッシュされていない)とstate (すぐにフラッシュされることが提案されている)のデータを混合して新しい状態を作成する場合など、これがどのように壊れる可能性があるかについてのより微妙なケースもあります: https ://github.com/facebook/react/issues/122#issuecomment-81856416。 参照には同じ問題があります: https ://github.com/facebook/react/issues/122#issuecomment-22659651。

これらの例はまったく理論的ではありません。 実際、React Reduxバインディングは、React小道具と非React状態が混在しているため、まさにこの種の問題を抱えていました: https : //github.com/ reactjs / /プル/ 99-Reduxのを反応させ、 https://github.com/reactjs/react-redux/issues/292 、 https://github.com/reactjs/redux/issues/1415 、 https://でgithubの。 com / reactjs / react-redux / issues / 525。

MobXユーザーがこれにぶつからない理由はわかりませんが、私の直感では、MobXユーザーはそのようなシナリオにぶつかる可能性がありますが、自分のせいであると考えています。 あるいは、 propsからあまり読み取らず、代わりにMobX可変オブジェクトから直接読み取る場合もあります。

では、Reactは今日これをどのように解決しますか? Reactでは、 this.stateとthis.props両方が調整とフラッシュの後にのみ更新されるため、リファクタリングの前後の両方で0が出力されます。 これにより、持ち上げ状態が安全になります。

はい、これは場合によっては不便です。 特に、単一の場所で完全な状態の更新を表す方法を考えるのではなく、状態を数回変更したいだけの、より多くのOOバックグラウンドから来ている人々にとって。 デバッグの観点からは、状態の更新を集中させる方が明確だと思いますが、共感できます: https ://github.com/facebook/react/issues/122#issuecomment-19888472。

それでも、特にレンダリングの信頼できる情報源として使用しない場合は、すぐに読み取りたい状態を横向きに変更可能なオブジェクトに移動するオプションがあります。 これは、MobXでできることとほぼ同じです🙂。

何をしているのかがわかっている場合は、ツリー全体をReactDOM.flushSync(fn)と呼ばれます。 まだ文書化していないと思いますが、16.xリリースサイクルのある時点で確実に文書化する予定です。 実際には、呼び出し内で発生する更新に対して完全な再レンダリングが強制されるため、使用は慎重に行う必要があります。 このようにして、 props 、 state 、およびrefs間の内部整合性の保証を破ることはありません。

要約すると、Reactモデルは常に最も簡潔なコードにつながるとは限りませんが、内部的に一貫性があり、状態を持ち上げても安全です。

同時更新の有効化

概念的には、Reactはコンポーネントごとに単一の更新キューがあるかのように動作します。 これが、議論がまったく理にかなっている理由です。更新が正確な順序で適用されることは間違いないので、 this.stateに更新をすぐに適用するかどうかについて議論します。 ただし、そうである必要はありません(笑)。

最近、私たちは「非同期レンダリング」について多くのことを話してきました。 それが何を意味するのかをうまく伝えることができていないことは認めますが、それがR&Dの性質です。概念的に有望と思われるアイデアを追求しますが、十分な時間を費やして初めてその意味を理解します。

「非同期レンダリング」について説明してきた1つの方法は、 ReactがsetState()呼び出しの発信元に応じて、イベントハンドラー、ネットワークレスポンス、アニメーションなどに応じて異なる優先順位を割り当てることができるという

たとえば、メッセージを入力している場合、 TextBoxコンポーネントのsetState()呼び出しはすぐにフラッシュする必要があります。 ただし、入力中に新しいメッセージMessageBubbleレンダリングを特定のしきい値(たとえば1秒)まで遅らせる方が、ブロックのために入力が途切れるよりもおそらく良いでしょう。糸。

特定の更新の優先度を「低く」すると、レンダリングを数ミリ秒の小さなチャンクに分割して、ユーザーが気付かないようにすることができます。

このようなパフォーマンスの最適化は、あまりエキサイティングで説得力がないように聞こえるかもしれません。 「MobXではこれは必要ありません。更新の追跡は、再レンダリングを回避するのに十分な速度です」と言うことができます。 すべての場合に当てはまるとは思いません(たとえば、MobXがどれほど高速であっても、DOMノードを作成し、新しくマウントされたビューのレンダリングを行う必要があります)。 それでも、それが真実であり、読み取りと書き込みを追跡する特定のJavaScriptライブラリにオブジェクトを常にラップしても問題ないと意識的に判断した場合は、これらの最適化の恩恵をあまり受けない可能性があります。

ただし、非同期レンダリングはパフォーマンスの最適化だけではありません。

たとえば、ある画面から別の画面に移動している場合を考えてみます。 通常、新しい画面のレンダリング中にスピナーを表示します。

ただし、ナビゲーションが十分に高速である場合(1秒以内)、スピナーを点滅させてすぐに非表示にすると、ユーザーエクスペリエンスが低下します。 さらに悪いことに、非同期の依存関係(データ、コード、画像)が異なる複数のレベルのコンポーネントがある場合、スピナーのカスケードが1つずつ短時間点滅することになります。 これは視覚的に不快であり、すべてのDOMリフローのために実際にはアプリの速度が低下します。 また、多くの定型コードのソースでもあります。

別のビューをレンダリングする単純なsetState()を実行するときに、更新されたビューの「バックグラウンド」でのレンダリングを「開始」できると便利ではないでしょうか。 自分で調整コードを記述せずに、更新に特定のしきい値(1秒など)を超えた場合にスピナーを表示するか、新しいサブツリー全体の非同期依存関係が満足。 さらに、「待機中」の間、「古い画面」はインタラクティブなままであり(たとえば、別のアイテムを選択して移行できます)、Reactは、時間がかかりすぎる場合はスピナーを表示する必要があることを強制します。

現在のReactモデルとライフサイクルの調整により、実際にこれを実装できることがわかりました。 @acdliteは過去数週間この機能に取り組んでおり、まもなくRFCを投稿

これが可能なのは、 this.stateがすぐにフラッシュされないためだけであることに注意してください。 すぐにフラッシュされた場合、「古いバージョン」が表示されてインタラクティブである間は、ビューの「新しいバージョン」のレンダリングをバックグラウンドで開始する方法がありません。 彼らの独立した状態の更新は衝突するでしょう。

このすべてを発表することに関して、 @ acdliteから雷を盗むことはしたくありませんが、これが少なくとも少しエキサイティングに聞こえることを願っています。 これはまだベーパーウェアのように聞こえるか、私たちが何をしているのか本当にわからないように聞こえるかもしれません。 今後数か月以内に他の方法で納得できることを願っています。また、Reactモデルの柔軟性を高く評価していただければ幸いです。 そして、私が理解している限りでは、状態の更新をすぐにフラッシュしないことで、少なくとも部分的にこの柔軟性が可能になります。

全てのコメント31件

@gaearonを待っています。

@Kaybaraxねえ、週末です;-)

@mweststrateああ! 私の悪い。 涼しい。

ここで手足に出て、同じティックで複数のsetStateをバッチ処理したためだと言います。

来週は休暇になりますが、おそらく火曜日に行くので、月曜日に返信しようと思います。

関数enqueueUpdate(component){
sureInjected();

//コードのさまざまな部分(ReactCompositeComponentなど)
// _renderValidatedComponent)renderの呼び出しはネストされていないと想定します。
//それが事実であることを確認します。 (これは、各トップレベルの更新によって呼び出されます
// setState、forceUpdateなどの関数; 作成と
//トップレベルのコンポーネントの破壊はReactMountで保護されています。)

if(!batchingStrategy.isBatchingUpdates){
batchingStrategy.batchedUpdates(enqueueUpdate、component);
戻る;
}

dirtyComponents.push(コンポーネント);
if(component._updateBatchNumber == null){
component._updateBatchNumber = updateBatchNumber + 1;
}
}

@mweststrateわずか2セント:それは非常に有効な質問です。
setStateが同期している場合、状態について推論する方がはるかに簡単であることに私たちは皆同意していると確信しています。
setStateを非同期にする理由が何であれ、チームがそれを導入する欠点と比較してうまく反応するかどうかはわかりません。たとえば、現在の状態について推論するのが難しいことや、開発者にもたらす混乱などです。

@mweststrate興味深いことに、私はここで同じ質問をしました: https : //discuss.reactjs.org/t/historic-reasons-behind-setstate-not-being-immediately-visible/8487

私は個人的にこの問題について他の開発者の混乱を経験してきました。 @gaearon時間があれば、これについての説明を得るのは素晴らしいことです:)

申し訳ありませんが、今年の終わりであり、GitHubなどで、休暇前に取り組んできたすべての作業をまとめようとして少し遅れています。

私はこのスレッドに戻ってそれについて議論するつもりです。 しかし、現在、 this.stateがいつどのように更新されるかに直接関係する非同期React機能に取り組んでいるため、これは少し動く目標でもあります。 何かを書くのに多くの時間を費やしたくはありません。そして、根本的な仮定が変わったので、それを書き直さなければなりません。 ですから、これを開いたままにしておきたいのですが、いつ決定的な答えが出せるかはまだわかりません。

だからここにいくつかの考えがあります。 これは決して完全な応答ではありませんが、何も言わないよりも役立つかもしれません。

まず、更新をバッチ処理するために調整を遅らせることが有益であることに同意すると思います。 つまり、 setState()同期的な再レンダリングは多くの場合非効率的であり、複数の更新が行われる可能性が高いことがわかっている場合は、更新をバッチ処理することをお勧めします。

例えば、我々はブラウザ内でならclickハンドラ、および両方のChildとParent呼んsetState我々は再レンダリングする必要はありません、 Child 2回使用し、代わりにそれらをダーティとしてマークし、ブラウザイベントを終了する前にそれらを一緒に再レンダリングすることを好みます。

あなたが求めているのは、なぜ同じこと(バッチ処理)を実行できないのに、調整の終了を待たずにsetState更新をthis.stateすぐに書き込むことができないのかということです。 明白な答えは1つではないと思いますが(どちらのソリューションにもトレードオフがあります)、考えられる理由がいくつかあります。

内部の一貫性を保証する

stateが同期的に更新されても、 propsは更新されません。 (親コンポーネントを再レンダリングするまでprops知ることはできません。これを同期的に行うと、バッチ処理はウィンドウの外に出ます。)

現在、Reactによって提供されるオブジェクト( state 、 props 、 refs )は、内部的に相互に

状態だけを使用する場合、(提案したように)

console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 2

ただし、この状態を解除していくつかのコンポーネントで共有する必要があるため、親に移動するとします。

-this.setState({ value: this.state.value + 1 });
+this.props.onIncrement(); // Does the same thing in a parent

setState()依存する典型的なReactアプリでは、これがます。

ただし、これはコードを壊します!

console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0

これは、提案したモデルでは、 this.stateはすぐにフラッシュされますが、 this.propsはフラッシュされないためです。 また、親を再レンダリングせずにthis.propsをすぐにフラッシュすることはできません。つまり、バッチ処理をあきらめる必要があります(場合によっては、パフォーマンスが大幅に低下する可能性があります)。

props (まだフラッシュされていない)とstate (すぐにフラッシュされることが提案されている)のデータを混合して新しい状態を作成する場合など、これがどのように壊れる可能性があるかについてのより微妙なケースもあります: https ://github.com/facebook/react/issues/122#issuecomment-81856416。 参照には同じ問題があります: https ://github.com/facebook/react/issues/122#issuecomment-22659651。

これらの例はまったく理論的ではありません。 実際、React Reduxバインディングは、React小道具と非React状態が混在しているため、まさにこの種の問題を抱えていました: https : //github.com/ reactjs / /プル/ 99-Reduxのを反応させ、 https://github.com/reactjs/react-redux/issues/292 、 https://github.com/reactjs/redux/issues/1415 、 https://でgithubの。 com / reactjs / react-redux / issues / 525。

MobXユーザーがこれにぶつからない理由はわかりませんが、私の直感では、MobXユーザーはそのようなシナリオにぶつかる可能性がありますが、自分のせいであると考えています。 あるいは、 propsからあまり読み取らず、代わりにMobX可変オブジェクトから直接読み取る場合もあります。

では、Reactは今日これをどのように解決しますか? Reactでは、 this.stateとthis.props両方が調整とフラッシュの後にのみ更新されるため、リファクタリングの前後の両方で0が出力されます。 これにより、持ち上げ状態が安全になります。

はい、これは場合によっては不便です。 特に、単一の場所で完全な状態の更新を表す方法を考えるのではなく、状態を数回変更したいだけの、より多くのOOバックグラウンドから来ている人々にとって。 デバッグの観点からは、状態の更新を集中させる方が明確だと思いますが、共感できます: https ://github.com/facebook/react/issues/122#issuecomment-19888472。

それでも、特にレンダリングの信頼できる情報源として使用しない場合は、すぐに読み取りたい状態を横向きに変更可能なオブジェクトに移動するオプションがあります。 これは、MobXでできることとほぼ同じです🙂。

何をしているのかがわかっている場合は、ツリー全体をReactDOM.flushSync(fn)と呼ばれます。 まだ文書化していないと思いますが、16.xリリースサイクルのある時点で確実に文書化する予定です。 実際には、呼び出し内で発生する更新に対して完全な再レンダリングが強制されるため、使用は慎重に行う必要があります。 このようにして、 props 、 state 、およびrefs間の内部整合性の保証を破ることはありません。

要約すると、Reactモデルは常に最も簡潔なコードにつながるとは限りませんが、内部的に一貫性があり、状態を持ち上げても安全です。

同時更新の有効化

概念的には、Reactはコンポーネントごとに単一の更新キューがあるかのように動作します。 これが、議論がまったく理にかなっている理由です。更新が正確な順序で適用されることは間違いないので、 this.stateに更新をすぐに適用するかどうかについて議論します。 ただし、そうである必要はありません(笑)。

最近、私たちは「非同期レンダリング」について多くのことを話してきました。 それが何を意味するのかをうまく伝えることができていないことは認めますが、それがR&Dの性質です。概念的に有望と思われるアイデアを追求しますが、十分な時間を費やして初めてその意味を理解します。

「非同期レンダリング」について説明してきた1つの方法は、 ReactがsetState()呼び出しの発信元に応じて、イベントハンドラー、ネットワークレスポンス、アニメーションなどに応じて異なる優先順位を割り当てることができるという

たとえば、メッセージを入力している場合、 TextBoxコンポーネントのsetState()呼び出しはすぐにフラッシュする必要があります。 ただし、入力中に新しいメッセージMessageBubbleレンダリングを特定のしきい値(たとえば1秒)まで遅らせる方が、ブロックのために入力が途切れるよりもおそらく良いでしょう。糸。

特定の更新の優先度を「低く」すると、レンダリングを数ミリ秒の小さなチャンクに分割して、ユーザーが気付かないようにすることができます。

このようなパフォーマンスの最適化は、あまりエキサイティングで説得力がないように聞こえるかもしれません。 「MobXではこれは必要ありません。更新の追跡は、再レンダリングを回避するのに十分な速度です」と言うことができます。 すべての場合に当てはまるとは思いません(たとえば、MobXがどれほど高速であっても、DOMノードを作成し、新しくマウントされたビューのレンダリングを行う必要があります)。 それでも、それが真実であり、読み取りと書き込みを追跡する特定のJavaScriptライブラリにオブジェクトを常にラップしても問題ないと意識的に判断した場合は、これらの最適化の恩恵をあまり受けない可能性があります。

ただし、非同期レンダリングはパフォーマンスの最適化だけではありません。

たとえば、ある画面から別の画面に移動している場合を考えてみます。 通常、新しい画面のレンダリング中にスピナーを表示します。

ただし、ナビゲーションが十分に高速である場合(1秒以内)、スピナーを点滅させてすぐに非表示にすると、ユーザーエクスペリエンスが低下します。 さらに悪いことに、非同期の依存関係(データ、コード、画像)が異なる複数のレベルのコンポーネントがある場合、スピナーのカスケードが1つずつ短時間点滅することになります。 これは視覚的に不快であり、すべてのDOMリフローのために実際にはアプリの速度が低下します。 また、多くの定型コードのソースでもあります。

別のビューをレンダリングする単純なsetState()を実行するときに、更新されたビューの「バックグラウンド」でのレンダリングを「開始」できると便利ではないでしょうか。 自分で調整コードを記述せずに、更新に特定のしきい値(1秒など)を超えた場合にスピナーを表示するか、新しいサブツリー全体の非同期依存関係が満足。 さらに、「待機中」の間、「古い画面」はインタラクティブなままであり(たとえば、別のアイテムを選択して移行できます)、Reactは、時間がかかりすぎる場合はスピナーを表示する必要があることを強制します。

現在のReactモデルとライフサイクルの調整により、実際にこれを実装できることがわかりました。 @acdliteは過去数週間この機能に取り組んでおり、まもなくRFCを投稿

これが可能なのは、 this.stateがすぐにフラッシュされないためだけであることに注意してください。 すぐにフラッシュされた場合、「古いバージョン」が表示されてインタラクティブである間は、ビューの「新しいバージョン」のレンダリングをバックグラウンドで開始する方法がありません。 彼らの独立した状態の更新は衝突するでしょう。

このすべてを発表することに関して、 @ acdliteから雷を盗むことはしたくありませんが、これが少なくとも少しエキサイティングに聞こえることを願っています。 これはまだベーパーウェアのように聞こえるか、私たちが何をしているのか本当にわからないように聞こえるかもしれません。 今後数か月以内に他の方法で納得できることを願っています。また、Reactモデルの柔軟性を高く評価していただければ幸いです。 そして、私が理解している限りでは、状態の更新をすぐにフラッシュしないことで、少なくとも部分的にこの柔軟性が可能になります。

Reactのアーキテクチャの背後にある決定についての素晴らしい詳細な説明。 ありがとう。

マーク

ありがとう、ダン。

私は❤️この問題。 素晴らしい質問と素晴らしい答え。 これは悪い設計上の決定だといつも思っていたので、今は考え直さなければなりません😄

ありがとう、ダン。

私はそれをasyncAwesomesetState:smileと呼んでいます:

私はすべてを最初に非同期で実装する必要があると思う傾向があります。同期操作が必要な場合は、非同期操作を完了を待つことでラップします。 非同期コードから同期コードを作成する方がはるかに簡単です(必要なのはラッパーだけです)。逆の場合(スレッド化に手を伸ばさない限り、基本的に完全な書き換えが必要です。これはまったく軽量ではありません)。

@gaearon詳細な説明に感謝します! それは長い間私を悩ませてきました(「正当な理由があるに違いありませんが、誰もどれを知ることができません」)。 しかし今ではそれは完全に理にかなっており、これが本当に意識的な決定であることがわかります:)。 幅広い回答をありがとうございました。本当に感謝しています。

または、小道具からあまり読み取らず、代わりにMobX可変オブジェクトから直接読み取る場合もあります。

これは確かに確かに真実だと思います。MobXでは、小道具は通常、単なるコンポーネント構成として使用され、ドメインデータは通常、小道具ではなく、コンポーネント間で渡されるドメインエンティティでキャプチャされます。

繰り返しになりますが、どうもありがとうございました!

@gaearon詳細で素晴らしい説明をありがとう。
ここにはまだ何か足りないものがありますが、私は理解していると思いますが、確認したいと思います。

イベントが「OutsideReact」に登録されている場合、それはおそらく、たとえばrefのaddEventListenerを介して行われることを意味します。 その後、バッチ処理は行われません。
このコードを考えてみましょう:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    this.refBtn.addEventListener("click", this.onClick);
  }

  componentWillUnmount() {
    this.refBtn.removeEventListener("click", this.onClick);
  }

  onClick = () => {
    console.log("before setState", this.state.count);
    this.setState(state => ({ count: state.count + 1 }));
    console.log("after setState", this.state.count);
  };

  render() {
    return (
      <div>
        <button onClick={this.onClick}>React Event</button>
        <button ref={ref => (this.refBtn = ref)}>Direct DOM event</button>
      </div>
    );
  }
}

「ReactEvent」ボタンをクリックすると、コンソールに次のように表示されます。
"before setState" 0
"after setState" 0
他のボタン「DirectDOMevent」をクリックすると、コンソールに次のように表示されます。
"before setState" 0
"after setState" 1

いくつかの小さな調査とソースコードの閲覧の後、私はこれがなぜ起こるのかを知っていると思います。
reactはイベントのフローを完全に制御できず、次のイベントがいつどのように発生するかを確認できないため、「パニックモード」として状態変更をすぐにトリガーします。

これについてどう思いますか? :考え:

@ sag1vは少し関連性がありますが、新しい質問のために新しい問題を開く方がおそらく明確です。 説明のどこかで#11527を使用して、これにリンクしてください。

@ sag1v @gaearonは、ここで非常に簡潔な返信をくれましたhttps://twitter.com/dan_abramov/status/949992957180104704 。 彼のこれに対する見方も、私にもっと具体的に答えると思います。

@mweststrate新しい問題を開くことを考えましたが、これが「なぜsetState非同期なのか」という質問に直接関係していることに気付きました。
この議論はsetState 「非同期」にする際の決定に関するものなので、いつ、なぜ「同期」にするかを追加しようと思いました。
私の投稿がこの問題に関連していることをあなたに納得させなかった場合、私は新しい問題を開いてもかまいません:wink:

@Kaybaraxそれは、あなたの質問が「_なぜ同期_」ではなく「_いつ同期_」だったからです。
私の投稿で述べたように、私は理由を知っていると思いますが、私

reactはイベントのフローを完全に制御できず、次のイベントがいつどのように発生するかを確認できないため、「パニックモード」として状態変更をすぐにトリガーします

ある種。 これは、 this.state更新に関する質問とは正確には関連していませんが。

あなたが求めているのは、Reactがバッチ処理を有効にする場合です。 Reactは現在、Reactが管理するイベントハンドラー内で更新をバッチ処理し

イベントハンドラーがReactによって設定されていない場合、現在は更新が同期されます。 待つのが安全かどうか、そして他の更新がすぐに行われるかどうかがわからないためです。

Reactの将来のバージョンでは、この動作は変更されます。 計画では、更新をデフォルトで低優先度として扱い、最終的に合体してバッチ処理し(たとえば、1秒以内)、すぐにフラッシュすることを選択します。 詳細については、 https :

素晴らしい!

その質問と回答は、より到達しやすい場所に文書化する必要があります。 私たちを啓発してくれてありがとう。

多くを学んだ 。 ありがとう

私の視点をスレッドに追加しようとしています。 私はMobXをベースにしたアプリに数か月取り組んでおり、ClojureScriptを何年も探索し、独自のReactの代替手段(Respoと呼ばれる)を作成しました。非常に短い時間でしたが、初期にReduxを試し、ReasonMLに賭けています。

Reactと関数型プログラミング(FP)を組み合わせる際の中心的なアイデアは、FPの法則に従うスキルを使用して、データをビューにレンダリングできるようにすることです。 純粋関数のみを使用する場合、副作用はありません。

Reactは純粋関数ではありません。 Reactは、コンポーネント内にローカル状態を組み込むことで、DOMやその他のブラウザーAPI(MobXにも対応)に関連するさまざまなライブラリーと対話できるため、Reactは不純になります。 ただし、ClojureScriptで試してみましたが、Reactが純粋な場合、副作用のある既存のライブラリとのやり取りが非常に難しいため、災害になる可能性があります。

したがって、Respo(私自身のソリューション)では、矛盾しているように見える2つの目標がありました。1) view = f(store)なので、ローカル状態は期待されません。 2)グローバルレデューサーですべてのコンポーネントUI状態をプログラムするのは好きではありません。それは、維持するのが難しい可能性があるためです。 最後に、 pathsしてグローバルストアでコンポーネントの状態を維持するのに役立つシンタックスシュガーが必要であることがわかりました。その間、Clojureマクロを使用してコンポーネント内に状態の更新を書き込みます。

つまり、私が学んだことは、ローカル状態は開発者エクスペリエンス機能であり、その下には、エンジンが深いレベルで最適化を実行できるようにするグローバル状態が必要です。 それで、MobXの人々はOOPを好みます、それは開発者の経験のためですか、それともエンジンのためですか?

ちなみに、あなたがそれを見逃した場合に備えて、私はReactとその非同期機能の将来について話しました:
https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html

ダン、あなたは私のアイドルです.....どうもありがとう。

このページは役に立ちましたか?
0 / 5 - 0 評価