ReactのsetStateがマクロタスクなのかマイクロタスクなのかについて詳しく話しましょう

ReactのsetStateがマクロタスクなのかマイクロタスクなのかについて詳しく話しましょう

序文

最近、友人が面接を受けたのですが、面接官が奇妙な質問をしました。それがタイトルに書いた質問です。

面接官がこの質問をするということは、おそらく React についてあまり知らないということでしょう。また、応募者が履歴書に React に精通していると書いてあるのを見て、この質問で応募者が本当に React に精通しているかを判断したいのかもしれません 🤣。

面接官は適切な質問をしていますか? §

面接官の質問は、setState がマクロタスクかマイクロタスクかということです。面接官の意見では、setState は非同期操作である必要があります。 setState が非同期操作であるかどうかを判断するには、まず実験を行うことができます。CRA を通じて新しい React プロジェクトを作成し、プロジェクト内の次のコードを編集します。

'react' から React をインポートします。
'./logo.svg' からロゴをインポートします。
'./App.css' をインポートします。

クラスAppはReact.Componentを拡張します。
  状態 = {
    カウント: 1000
  }
  与える() {
    戻る (
      <div className="アプリ">
        <画像
          src={logo} alt="ロゴ"
          className="アプリロゴ"
          onClick={this.handleClick}
        />
        <p>私のフォロワー数: {this.state.count}</p>
      </div>
    );
  }
}

デフォルトのアプリをエクスポートします。

ページはおそらく次のようになります:

上記の React ロゴはクリック イベントにバインドされています。次に、このクリック イベントを実装する必要があります。ロゴをクリックした後、setState 操作を実行し、設定操作が完了したらログを出力し、設定操作の前にマクロ タスクとマイクロ タスクを追加します。コードは次のとおりです。

ハンドルクリック = () => {
  const ファン = Math.floor(Math.random() * 10)
  タイムアウトを設定する(() => {
    console.log('マクロタスクがトリガーされました')
  })
  Promise.resolve().then(() => {
    console.log('マイクロタスクトリガー')
  })
  this.setState({
    カウント: this.state.count + ファン
  }, () => {
    console.log('新しいファン:', ファン)
  })
}

当然のことながら、ロゴをクリックすると、まず setState 操作が完了し、その後マイクロタスクとマクロタスクがトリガーされます。そのため、setState の実行時間はマイクロタスクやマクロタスクよりも早くなります。それでも、その実行時間は Promise.then よりも早いとしか言​​えず、同期タスクであることを証明することはできません。

ハンドルクリック = () => {
  const ファン = Math.floor(Math.random() * 10)
  console.log('実行を開始します')
  this.setState({
    カウント: this.state.count + ファン
  }, () => {
    console.log('新しいファン:', ファン)
  })
  console.log('実行終了')
}

この観点から見ると、setState も非同期操作であると思われます。主な理由は、React のライフサイクルとバインドされたイベント フローで、すべての setState 操作が最初にキューにキャッシュされるためです。イベント全体が終了するか、マウント プロセスが終了すると、以前にキャッシュされた setState キューが計算のために取り出され、状態の更新がトリガーされます。 React のイベントフローまたはライフサイクルから抜け出す限り、React の setState に対する制御を解除できます。最も簡単な方法は、setState を setTimeout の匿名関数に配置することです。

ハンドルクリック = () => {
  タイムアウトを設定する(() => {
    const ファン = Math.floor(Math.random() * 10)
    console.log('実行を開始します')
    this.setState({
      カウント: this.state.count + ファン
    }, () => {
      console.log('新しいファン:', ファン)
    })
    console.log('実行終了')
  })
}

setState は本質的にはまだイベント ループ内にあり、別のマクロタスクやマイクロタスクに切り替えられていないことがわかります。動作は同期コードに基づいて実装されていますが、非同期動作のように見えます。したがって、面接官に対する疑問はまったくありません。

React は setState をどのように制御しますか? §

前のケースでは、setState は setTimeout 内でのみ同期メソッドのようになります。これはどのように行われるのでしょうか?

ハンドルクリック = () => {
  // 通常操作 this.setState({
    カウント: this.state.count + 1
  })
}
ハンドルクリック = () => {
  // React 制御外の操作 setTimeout(() => {
    this.setState({
      カウント: this.state.count + ファン
    })
  })
}

前のコードを確認しましょう。これら 2 つの操作では、2 つのコール スタックの違いを確認するために、パフォーマンスでコール スタックをそれぞれ 1 回記録します。

コール スタックでは、Component.setState メソッドが最終的に enqueueSetState メソッドを呼び出し、enqueueSetState メソッドが scheduleUpdateOnFiber メソッドを呼び出すことがわかります。違いは、通常の呼び出し中、scheduleUpdateOnFiber メソッドは EnsureRootIsScheduled のみを呼び出し、flushSyncCallbackQueue メソッドはイベント メソッドの終了後に呼び出されることにあります。 React イベント フローを終了すると、scheduleUpdateOnFiber は、ensureRootIsScheduled 呼び出しが完了した後、flushSyncCallbackQueue メソッドを直接呼び出します。このメソッドは、状態を更新して再レンダリングするために使用されます。

関数scheduleUpdateOnFiber(ファイバー、レーン、イベント時間) {
  レーン === SyncLane の場合 {
    // 同期操作 EnsureRootIsScheduled(root, eventTime);
    // React イベント ストリームにまだ存在するかどうかを確認します // そうでない場合は、flushSyncCallbackQueue を直接呼び出して更新します if (executionContext === NoContext) {
      同期コールバックキューをフラッシュします。
    }
  } それ以外 {
    // 非同期操作}
}

上記のコードは、主に、executionContext が NoContext と等しいかどうかを判断し、現在の更新プロセスが React イベント フロー内にあるかどうかを判断するこのプロセスを簡単に記述できます。

ご存知のとおり、React はイベントをバインドするときに、イベントを合成してドキュメントにバインドし (react@17 では、レンダリング時に指定された DOM 要素にイベントをバインドするように変更されました)、最終的に React がイベントをディスパッチします。

すべてのイベントがトリガーされると、最初に batchedEventUpdates$1 メソッドが呼び出され、executionContext の値が変更され、React はこの時点で setState が制御下にあることを認識します。

//executionContext のデフォルト状態 var executeContext = NoContext;
関数 batchedEventUpdates$1(fn, a) {
  var 前の実行コンテキスト = 実行コンテキスト;
  実行コンテキスト |= イベントコンテキスト; // ステータスを変更する try {
    fn(a) を返します。
  ついに
    実行コンテキスト = 前の実行コンテキスト;
    // 呼び出しが完了したら、flushSyncCallbackQueue を呼び出します
    実行コンテキストがコンテキストなしの場合
      同期コールバックキューをフラッシュします。
    }
  }
}

したがって、flushSyncCallbackQueue を直接呼び出すか、呼び出しを延期するかに関係なく、基本的には同期的ですが、順序の問題があります。

将来的には非同期setStateが実装される予定§

上記のコードをよく見ると、scheduleUpdateOnFiber メソッドでレーンの同期が判断されるので、非同期の状況があるかどうかがわかります。

関数scheduleUpdateOnFiber(ファイバー、レーン、イベント時間) {
  レーン === SyncLane の場合 {
    // 同期操作 EnsureRootIsScheduled(root, eventTime);
    // React イベント ストリームにまだ存在するかどうかを確認します // そうでない場合は、flushSyncCallbackQueue を直接呼び出して更新します if (executionContext === NoContext) {
      同期コールバックキューをフラッシュします。
    }
  } それ以外 {
    // 非同期操作}
}

React が 2 年前にファイバー アーキテクチャをアップグレードしたとき、非同期性に対応する準備が進められていました。 Concurrent モードは React 18 で正式にリリースされます。Concurrent モードの公式紹介は次のとおりです。

同時実行モードとは何ですか?

並行モードは、ユーザーのデバイス機能とネットワーク速度に基づいてアプリの応答性を維持し、適切にスケーリングするのに役立つ新しい React 機能のセットです。並行モードでは、レンダリングは非ブロッキングです。中断可能です。これにより、ユーザーエクスペリエンスが向上します。また、これまでは不可能だった新しい機能も実現します。

現在、並列モードを使用する場合は、React の実験バージョンを使用する必要があります。この部分に興味がある方は、私の以前の記事を読んでみてください: https://blog.shenfq.com/posts/2020/React%20The Evolution of Architecture%20-%20From Synchronous to Asynchronous.html

要約する

React の setState がマクロタスクかマイクロタスクかについてはこれで終わりです。React の setState がマクロタスクかマイクロタスクかについての詳細は、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • ReactのsetStateコールバック関数の詳細な説明
  • React の状態と setState の使用に関する学習ノート
  • React.setStateを使用する際に注意すべき3つのポイントについて簡単に説明します。
  • ReactのsetStateの動作メカニズムの詳細な理解
  • ReactのsetStateソースコードの詳細な研究
  • ReactでのsetStateの使用と同期と非同期の使用

<<:  Alibaba Cloud によって追加されたセキュリティ グループ ポートと、追加後にアクセスできない問題のトラブルシューティング

>>:  MySql ストレージ エンジンとインデックスに関する知識のまとめ

推薦する

MySQLカバーインデックスの使用例

カバーインデックスとは何ですか?クエリで使用されるすべてのフィールドを含むインデックスを作成すること...

MySql の忘れたパスワードの変更方法はバージョン 5.7 以上に適しています

1. まずmysqld.exeプロセスを停止します2. cmd を開き、mysql の bin ディ...

dockerにmysqlをインストールした後にNavicatが接続できない問題に対する完璧な解決策

1. Dockerがイメージをプルするdocker pull mysql (デフォルトで最新バージョ...

テーブル設定の背景画像が100%表示されない解決策

開発中に以下の状況が発見されました。 (1) ファイルが.jspファイル拡張子で保存されている場合、...

Vue v-for ループを書く 7 つの方法

目次1. v-forループでは常にキーを使用する2. 特定のスコープ内でv-forループを使用する3...

JavaScript における Arguments オブジェクトの使用に関する詳細な説明

目次序文議論の基本概念議論の役割実パラメータと仮パラメータの数を取得する実際のパラメータ値を変更する...

Vueコンポーネントは、写真やビデオをアップロードするためのサンプルコードをカプセル化します

まず依存関係をダウンロードします: cnpm i -S vue-uuid ali-oss画像フィール...

ハイパーコネクションの4つの状態の適用の詳細な説明

ブラウザの問題かもしれないと思うかもしれませんが、スタイル定義の順序が間違っている可能性が高いです。...

Linux 名前空間ユーザーの詳細な説明

ユーザー名前空間は Linux 3.8 で追加された新しい名前空間で、ユーザー ID やグループ I...

Vueのコンポーネント値の転送から始まるオブザーバーモードの詳細な説明

目次オブザーバーパターンVue パス値最初のステップは、main.jsにバスを登録することです。 2...

MySQL Strict Modeの知識ポイントの詳細な説明

I. 厳密モードの説明MySQL 5.0 以降の厳密モード (STRICT_TRANS_TABLES...

60件のページング事例と優れた実践例を推奨

<br />構造と階層により複雑さが軽減され、読みやすさが向上します。記事やサイトが整理...

MySQL データベース クエリ パフォーマンス最適化戦略

クエリを最適化するExplain ステートメントを使用してクエリ ステートメントを分析するExpla...

VMware esxi6.5 のインストールと使用の詳細な手順

目次導入建築ESXIの利点vSphere とは何ですか? 2. 仮想マシンの利点3. 仮想マシンを使...

LinuxベースのLVMシームレスディスク水平拡張の詳細な説明

環境名前財産CPU 5650 円メモリ4Gディスク20G+4TB この時点で、サーバーにはすでに次の...