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

推薦する

Centos6.6 で php7 + nginx 環境をインストールする方法

この記事では、centos6.6 で php7 + nginx 環境をインストールする方法について説...

jsはテーブルドラッグオプションを実装します

この記事の例では、テーブルドラッグオプションを実装するためのjsの具体的なコードを参考までに共有して...

上部の固定ナビゲーションバーによって CSS アンカーの配置がブロックされる問題の解決方法

多くのウェブサイトでは、ユーザーが簡単に検索したり他のページに移動したりできるように、上部にナビゲー...

CSS 位置プロパティが絶対の場合のパーセンテージ値の計算

位置が絶対の場合、関連する属性のパーセンテージは、参照先の要素 (包含ブロック) を基準として計算さ...

HTMLテーブルタグの詳しい解説(初心者向け)

表> <TR> <TD> <TH> <キャプション&...

Linux resolv.conf の簡単な分析

1. はじめにresolv.conf は、さまざまなオペレーティング システムのドメイン ネーム シ...

MySQL で結合を使用して SQL を最適化する方法の詳細な説明

0. 以下のテストに関連する表を準備する関連するテーブル作成ステートメントについては、https:/...

Tomcatの起動が遅い問題を素早く解決、超簡単

今日、私はクラスメートが問題を解決するのを手伝いました - Tomcat の起動が非常に遅く、約 5...

JSはシンプルなカウンターを実装します

HTML CSS および JavaScript を使用して、プラス、マイナス、ゼロの 3 つのボタン...

CSS ボックスモデル内のパディングと略語の詳細な説明

上図のように、パディング値は時計回り(右上、右下)の複合属性であり、パディングの内側の余白がボックス...

Centos7 FFmpeg オーディオ/ビデオ ツールのインストールに関する簡単なドキュメント

ffmpeg は非常に強力なオーディオおよびビデオ処理ツールです。公式 Web サイトは http:...

Javascript 非同期プログラミング: Promise を本当に理解していますか?

目次序文基本的な使い方文法エラー処理プロミスチェーン呼び出し非同期と待機よく使われる方法1. Pro...

Vue プロジェクトで mock.js を使用するための完全な手順

Vue プロジェクトで mock.js を使用する開発ツールの選択: Vscode 1. コマンドラ...

Linux での MySQL のアンインストールとインストールのグラフィック チュートリアル

ブログを書くのは初めてです。開発に携わって2年になります。仕事の後に何か有意義なことを見つけたいと思...

画像をMySQLデータベースに保存し、フロントエンドページに表示するための実装コード

目次1. まず、pycharmを使用してDjangoプロジェクトを作成し、関連する環境を設定します。...