web-dev-qa-db-ja.com

非同期待機がReact setStateで機能するのはなぜですか?

ReactJSプロジェクトでは、abelでawaitを使用しています。 React setStateの便利な使い方を発見しました。次のコードを検討してください。

handleChange = (e) => {
  this.setState({[e.target.name]: e.target.value})
  console.log('synchronous code')
}

changeAndValidate = async (e) => {
  await this.handleChange(e)
  console.log('asynchronous validation code')
}

componentDidUpdate() {
  console.log('updated component')    
}

私の意図は、コンポーネントの更新後に非同期検証コードを実行することでした。そしてそれは動作します!結果のコンソールログは次を示します:

synchronous code
updated component
asynchronous validation code

検証コードは、handleChangeが状態を更新し、新しい状態がレンダリングされた後にのみ実行されます。

通常、状態が更新された後にコードを実行するには、this.setStateの後にコールバックを使用する必要があります。つまり、handleChangeの後に何かを実行する場合は、コールバックパラメーターを指定してからsetStateに渡す必要があります。可愛くない。しかし、コード例では、状態が更新された後にhandleChangeが完了したことを何らかの方法で待機しています...しかし、待機はプロミスでのみ機能し、続行する前にプロミスが解決するのを待つと思いました。 handleChangeには約束も解決もありません...何を待つべきかをどのように知るのですか?

これは、setStateが非同期に実行され、awaitが完了したことを何らかの形で認識していることを意味するようです。 setStateが内部でpromiseを使用している可能性がありますか?

バージョン:

反応:「^ 15.4.2」

babel-core: "^ 6.26.0"

babel-preset-env: "^ 1.6.0"、

babel-preset-react: "^ 6.24.1"、

babel-preset-stage-0: "^ 6.24.1"

babel-plugin-system-import-transformer: "^ 3.1.0"、

babel-plugin-transform-decorators-legacy: "^ 1.3.4"、

babel-plugin-transform-runtime: "^ 6.23.0"

27
Leo Fabrikant

Davinの答えを単純化および補完するために最善を尽くしたので、実際にここで何が起こっているのかをよりよく理解することができます。


  1. awaitthis.handleChangeの前に置かれ、これはscheduleの残りの実行changeAndValidate関数awaitresolvesの右側に指定された値、この場合はthis.handleChangeによって返された値の場合にのみ実行
  2. this.handleChangeawaitの右側で、次を実行します。

    2.1。 setStateはアップデーターを実行しますが、setStateはすぐに更新することを保証しないため、更新が後で発生するようにスケジュールする可能性があります(即時であるか後の時点であるかは関係ありません、重要なのはスケジュールされていることだけです)

    2.2。 console.log( 'synchronous code')実行...

    2.3。 this.handleChangeその後、リターンを終了ndefined(明示的に指定されていない限り、関数はundefinedを返すため、undefinedを返します)

  3. await次にこれを取りますndefinedそして、それは約束ではないので、それを解決された約束に変換しますPromise.resolve(undefined)を使用して、それ-舞台裏で非同期の。thenメソッドに渡されるため、すぐには利用できません:

「Promiseに渡されたコールバックは、JavaScriptイベントループの現在の実行が完了する前に呼び出されることはありません。」

3.1。これは、-ndefinedevent queueの後ろに配置されることを意味します(つまり、イベントでsetStateアップデータの背後にあることを意味します)キュー…)

  1. イベントループ最後に到達し、setStateアップデートを取得します。

  2. イベントループ到達してピックアップ未定義、これは未定義に評価されます(必要であればこれを保存できます) 、したがって、=は解決された結果を保存するためにawaitの前に一般的に使用されます)

    5.1。 Promise.resolve()が終了しました。つまり、awaitはもはや有効ではないため、残りの関数は再開できます。

  3. 検証コードの実行
32
linasmnew

私はまだこれをテストしていませんが、ここで私が起こっていると思うことは次のとおりです。

undefinedによって返されるawaitは、setStateコールバックの後にキューに入れられます。 awaitは(Promise.resolveの下で)regenerator-runtimeを実行しており、イベントループの次のアイテムに制御を渡します。

したがって、setStateコールバックがawaitより先にキューに入れられることは偶然です。

これをテストするには、setStateの周りにsetTimeout(f => f、0)を配置します。

babelregenerator-runtimeは、基本的にPromise.resolveを使用して制御を生成するループです。 _ asyncToGenerator の内部を見ることができ、Promise.resolveがあります。

4
Davin Tryon

rvまたはawaitの戻り値は次のように定義されます。

rv
Returns the fulfilled value of the promise, or the value itself if it's not a Promise.

したがって、handleChangeは非同期値でもプロミス値でもないため、単純に自然な値を返します(この場合、戻り値はないので、undefined)。したがって、「handleChangeが完了したことを知らせる」ための非同期イベントループトリガーはありません。単に指定した順序で実行されるだけです。

1
Sterling Archer

setState()は常にコンポーネントをすぐに更新するわけではありません doc

しかし、ここではそうかもしれません。

コールバックをpromiseに置き換えたい場合は、自分で実装できます。

setStateAsync(state) {
  return new Promise((resolve) => {
    this.setState(state, resolve)
  });
}

handleChange = (e) => {
  return this.setStateAsync({[e.target.name]: e.target.value})
}

参照: https://medium.com/front-end-hacking/async-await-with-react-lifecycle-methods-802e7760d802

1
Gabriel Bleu