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 ストレージ エンジンとインデックスに関する知識のまとめ

推薦する

ブラウザのURLの前に小さなアイコンを表示する方法

多くのウェブサイトを閲覧すると、ブラウザのアドレスバーの前に小さなアイコンがあり、ブラウザのタブの位...

Windows 10 での mysql-8.0.17-winx64 のインストール方法

1.公式サイトからダウンロードして解凍する参考: ダウンロード後、zip 圧縮ファイル (mysql...

ウェブページのテーブルの境界線を設定する方法

<br />前回は、Web テーブルにセルの線を設定する方法を学びました。今日は、Web...

HTML における相対と絶対の使用法と違いの詳細な説明

HTML における相対と絶対の違い: 正直に言うと、HTML は世界で最もシンプルな言語です。タグ言...

Tomcat でサーブレット URL パターンを構成する際の問題の詳細な説明

tomcat の web.xml を構成する場合、サーブレットは比較的重要な問題です。ここでは、サー...

Navicat を仮想マシン MySQL に接続する際によくあるエラーと解決策

質問1 解決するサービスを開始します: service mysqld start; /sbin/ip...

CSS ワールド - コード実践: 画像の Alt 情報の表示

ただし、デフォルトの src を持つ <img> 要素を使用してスクロール読み込み効果を...

docker イメージのプル速度が遅い問題の解決策

現在、Docker には中国向けの公式ミラーがあります。詳細については、https://www.do...

MySql 8.0.11 のインストール プロセスと Navicat とのリンク時に発生する問題の概要

私のシステムとソフトウェアのバージョンは次のとおりです。システム環境: win7、64ビットMySQ...

シェルスクリプトを使用して CentOS7 に python3.8 環境をインストールする (推奨)

ワンクリック実行仮想マシンに Python 3.8 をインストールするには、ネットワーク アダプター...

MySQLの結合の基本原理についての簡単な説明

目次結合アルゴリズム駆動テーブルと非駆動テーブルの違い1. 単純なネストループ結合、単純なネスト、イ...

ウェブページのドロップダウンリストとdivレイヤーのカバーの問題を選択する

HTML の select 要素に関する質問は、さまざまな場所で提起されています。最近のプロジェクト...

React 高階コンポーネント HOC 使用方法の概要

HOCを紹介する一文高階コンポーネント (HOC) とは何ですか? 公式ドキュメントによると、「高階...

sqlmap インジェクションの詳細なグラフィック説明

目次1. この Web サイトには SQL インジェクションの脆弱性がある可能性があることが判明しま...

MySQL ログの設定と表示方法

MySQL には次のログがあります。エラーログ: -log-errクエリログ: -logスロークエリ...