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

推薦する

メタビューポートタグ(モバイルブラウジングズームコントロール)の使用方法

OP が現在のファームウェアで Web ページを開くと、常に 50% にズームアウトされてから表示さ...

Linux システムで grub.cfg ファイルの破損を修復する手順

目次1. grub.cfg ファイルの紹介1. grub.cfg ファイルの場所2. grub.cf...

Nginx リバース プロキシを使用して go-fastdfs を実行する例

背景go-fastdfs は、http プロトコルをサポートする分散ファイルシステムです。一般的なプ...

Linux環境でOpenSSL証明書を生成する

1. 環境: CentOS7、OpenSSL1.1.1k。 2. コンセプト:ルート証明書: サーバ...

ウェブ開発者はIE7とIE8の共存を懸念している

今日、IE8 をインストールしました。ダウンロードするために Microsoft の Web サイト...

mysql コマンドライン スクリプトの実行例

この記事では、例を使用して MySQL コマンドライン スクリプトの実行について説明します。ご参考ま...

Zabbixについて管理者ログインパスワードを忘れた場合、パスワードをリセットする

Zabbix 管理者ログイン パスワードのリセットに関する問題は次のとおりです。 1. 問題の説明:...

HTML リスト タグ dl、ul、ol の使用例

コードをコピーコードは次のとおりです。 <!--リストタグ: <dl>: 階層リス...

Mysql のいくつかの複雑な SQL ステートメント (重複行のクエリと削除)

1. 重複行を見つける blog_user_relation a から * を選択 WHERE (...

mysql トリガーの作成と使用例

目次トリガーとは何かトリガーを作成するMySQL 作成構文のキーワードの説明: 1. MySQL ト...

自作の Windows サーバーに egg アプリケーションを展開する方法 (画像とテキスト付き)

1. IEブラウザを使用してVPNにログインする 2. リモートログイン 3. サーバーに最新のn...

MySQL の時間保持問題に関する簡単な分析

MySQL のデフォルトの時間タイプ (datetime と timestamp) の精度は秒です。...

MySQL 5.7.20\5.7.21 無料インストール版のインストールと設定のチュートリアル

参考までに、mysql 5.7.20 / 5.7.21 をダウンロード、インストール、構成します。具...

LinuxにPython 3.6をインストールして落とし穴を避ける

Python 3のインストール1. 依存環境をインストールするPython3 はインストール プロセ...

Nginxはhttpとhttpsの両方のアクセスをサポートするために同じドメイン名を設定します

Nginx は同じドメイン名で構成されており、http と https の両方でアクセスできます。証...