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のkey_lenの計算方法についての簡単な説明

MySQL の explain コマンドは SQL のパフォーマンスを分析できます。その 1 つが ...

MySQL 8.0.12 のインストールと設定方法のグラフィックチュートリアル

MySQL 8.0.12のインストールと設定方法を記録してみんなで共有します。 1. インストール1...

Linux での MongoDB のインストールに関するチュートリアル

MongoDB はクロスプラットフォームであり、Windows と Linux の両方にインストール...

MySQLでユーザーを作成し、権限を管理する方法

1. ユーザーとパスワードの作成方法1. MySQLデータベースに入る mysql> mysq...

nacos が mysql に接続できない場合の解決策

理由nacos の pom が依存する mysql バージョンが、mysql バージョンと一致してい...

HTML フォームタグチュートリアル (5): テキストフィールドタグ

<br />このタグは、さらにテキストを入力できる複数行のテキスト フィールドを作成する...

JavaScript で外部変数にアクセスするサブ関数の 3 つのソリューション

序文Web ページを作成するときに、次のような状況に遭遇することはよくあります。 <本文>...

js における浅いコピーと深いコピーの詳細な説明

目次1. jsメモリ2. 譲渡3. 浅いコピー4. ディープコピー序文:以下の記事を読む前に、記憶に...

VMWare 仮想マシン 15.X LAN ネットワーク構成チュートリアル図

最近、分散型およびビッグデータ技術について学ぶために、いくつかの仮想マシンに取り組んでいます。まず、...

ウェブページの HTML コード: スクロールテキストの作成

このセクションでは、Web ページ内のテキストをスクロールしたり、スクロール プロパティを制御できる...

CSS3で実装された水平ヘッダーメニュー

結果:実装コードhtml <nav class="dropdownmenu"...

MySQL ビューの一貫性を確保する方法の詳細な説明 (チェック オプション付き)

この記事では、例を使用して、MySQL ビューの一貫性を確保する方法 (チェック オプションを使用)...

jQuery で呼吸カルーセル効果を実現

この記事では、呼吸カルーセル効果を実現するためのjQueryの具体的なコードを参考までに共有します。...

MySQL MHA の高可用性構成とフェイルオーバーの詳細な導入手順

目次1. MHAの紹介1. MHAとは何ですか? 2. MHAの構成3. MHAの特徴2. MySQ...

WeChatアプレットの入力レベルとテキストエリアレベルの浸透率が高すぎる問題の解決策

WeChat ミニプログラムのネイティブ コンポーネントであるカメラ、キャンバス、入力 (フォーカス...