React Diff Principle の詳細な分析

React Diff Principle の詳細な分析

Diffを理解する前に、Reactの仮想DOMの構造を見てみましょう。

これはHTML構造です

<div id="父">
  <p class="child">私は子供ですp</p>
  <div class="child">私は子divです</div>
</div>

これはReactがHTMLをレンダリングするときのjsコードです。babelで試すことができます。

React.createElement("div", {id: "father"}, 
    React.createElement("p", {class: "child"}, "私は子供pです"),             
    React.createElement("div", {class: "child"}, "私は子divです")
);

これはツリー構造であることを示しています。

React は、render メソッドを呼び出すときにツリー (pre と呼ばれる) を作成し、次に render メソッドが呼び出されたときに別のツリー (cur と呼ばれる) を返します。 React は、pre ツリーと cur ツリーの違いを比較して、UI を効率的に更新する方法を決定し、現在の UI が最新のツリー cur と同期されるようにします。

UI を効率的に更新するために、React は次の 2 つの仮定に基づいた O(n) ヒューリスティック アルゴリズムを提案します。

1. 2 つの異なるタイプの要素によって異なるツリーが生成されます。

2. 開発者はキー属性を設定して、異なるレンダリングでどのサブ要素を変更せずに保持できるかをレンダラーに伝えることができます。

差分アルゴリズム

レイヤーごとの比較

2 つのツリーを比較する場合、React はレイヤーごとに比較し、同じカラー ボックス内の DOM ノードのみを比較します。

まず、2 つのツリーのルート ノードを比較します。ルート ノードの種類によって形状が異なります。ルートノードが異なるタイプの要素である場合、React は元のツリーを破棄して新しいツリーを構築します。たとえば、要素が <a> から <img>、<Article> から <Comment>、または <Button> から <div> に変更されると、完全な再構築プロセスがトリガーされます。

//前に
<div>
    <アプリ/>
</div>
//後
<p>
    <アプリ/>
</p>

React は App コンポーネントを破棄し (そのサブコンポーネントもすべて破棄されます)、新しい App コンポーネント (そのサブコンポーネントを含む) を再作成します。

次の DOM 構造変換:

React は、同じレイヤー内のノードの位置の変更のみを単純に考慮します。異なるレイヤー内のノードの場合は、単純な作成と削除のみになります。ルート ノードは、子ノードに A がないことを検出すると、A を直接破棄します。また、D は、余分な子ノード A があることを検出すると、子ノードとして新しい A を作成します。したがって、この構造変換の実際の操作は次のようになります。

A.破棄();
A = 新しいA();
A.append(新しいB());
A.append(新しいC());
D.append(A);

このアルゴリズムは少し「粗雑」に見えますが、2 つの異なるタイプの要素が異なるツリーを生成するという最初の仮定に基づいています。 React の公式ドキュメントによると、この仮定によって今のところ重大なパフォーマンスの問題は発生していないそうです。もちろん、これは、安定した DOM 構造を維持することが、独自のコンポーネントを実装する際のパフォーマンスの向上に役立つというヒントも与えてくれます。たとえば、DOM ノードを実際に削除したり追加したりするのではなく、CSS を使用して特定のノードを非表示にしたり表示したりできる場合があります。

同じタイプのコンポーネントを比較する

コンポーネントが更新されると、コンポーネント インスタンスは変更されませんが、状態または props のデータが変更されると、render が呼び出され、コンポーネント内の子要素が更新されます。

同じタイプの要素の比較

同じタイプの 2 つの React 要素を比較する場合、React は DOM ノードを保持し、変更されたプロパティのみを比較して更新します。

<div className="before" title="内容" />

<div className="after" title="内容" />

React は、div の className を before から after に変更します (React で状態を更新するマージ操作に似ています)。

子ノードを再帰的に

デフォルトでは(レベルごとの比較)、DOM ノードの子を再帰的に処理する場合、React は両方の子のリストを同時に走査します。違いが見つかると、ミューテーションが生成されます。

したがって、リストの末尾に要素を追加する場合、更新のオーバーヘッドは比較的小さくなります。例えば:

<ul>
  <li>まず</li>
  <li>2番目</li>
</ul>

<ul>
  <li>まず</li>
  <li>2番目</li>
  <li>3番目</li>
</ul>

React は最初に 2 つの <li>first</li> ツリーを一致させ、次に 2 番目の要素 <li>second</li> のツリーを一致させ、最後に 3 番目の要素の <li>third</li> ツリーを挿入します。

新しい要素をテーブル ヘッダーに単純に挿入すると、更新のオーバーヘッドが比較的大きくなります。例えば:

<ul>
  <li>デューク</li>
  <li>ヴィラノバ</li>
</ul>

<ul>
  <li>コネチカット</li>
  <li>デューク</li>
  <li>ヴィラノバ</li>
</ul>

React は、<li>Duke</li> と <li>Villanova</li> を保持する必要があることに気付かず、それぞれの子を再構築します。この状況はパフォーマンスの問題を引き起こす可能性があります。

キー

上記の問題を解決するために、React は key 属性を導入しました。子にキーがある場合、React はそのキーを使用して、古いツリーの子と最新のツリーの子を一致させます。次の例では、キーを追加した後のツリー変換の効率が向上します。

<ul>
  <li key="2015">デューク</li>
  <li key="2016">ヴィラノバ</li>
</ul>

<ul>
  <li key="2014">コネチカット</li>
  <li key="2015">デューク</li>
  <li key="2016">ヴィラノバ</li>
</ul>

これで、React は「2014」キーを持つ要素だけが新しく、「2015」キーと「2016」キーを持つ要素は単に移動しただけであることを認識します。したがって、key=2014 の要素のみが作成され、残りの 2 つの要素は作成されません。

したがって、配列の順序が変わる可能性があるため、配列の添え字をキー値として使用しないことをお勧めします。データ自体に含まれる一意の識別子 (ID またはその他の属性) を使用するのが最適です。

1. 仮想DOMにおけるキーの役割:
1) 簡単に言うと、キーは仮想 DOM オブジェクトの識別子であり、表示を更新するときにキーが非常に重要な役割を果たします。

2) 詳細: 状態内のデータが変更されると、React は新しいデータに基づいて新しい仮想 DOM を生成し、新しい仮想 DOM と古い仮想 DOM の diff 比較を実行します。比較ルールは次のとおりです。

a. 新しい仮想 DOM と同じキーが古い仮想 DOM に見つかります。
(1)仮想DOMの内容が変更されていない場合は、以前の実DOMを直接使用する
(2)仮想DOMの内容が変更されると、新しい実DOMが生成され、ページ内の以前の実DOMが置き換えられます。

b. 新しい仮想DOMと同じキーが古い仮想DOMに見つからない
データに基づいて新しい実際のDOMを作成し、それをページにレンダリングします。

2. インデックスをキーとして使用する場合に発生する可能性のある問題:
1. 逆の順序でデータを追加または削除するなど、データの順序を破壊する操作を実行した場合:
不要な実際の DOM 更新が生成されます ==> インターフェース効果は良好ですが、効率は低くなります。

2. 構造に入力クラスの DOM も含まれている場合:
間違った DOM 更新が発生します ==> インターフェイスに問題があります。
3. 注意!逆順にデータを追加したり削除したりするなど、順序を崩す操作がない場合、
これは表示用のリストをレンダリングするためにのみ使用されるため、インデックスをキーとして使用しても問題はありません。

上記は、React Diff 原則の徹底的な分析の詳細な内容です。React Diff 原則の詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • Reactの仮想DOMとdiffアルゴリズムの詳細な説明
  • React diffアルゴリズムソースコード分析
  • ReactアプリケーションにおけるDOM DIFFアルゴリズムの詳細な説明
  • ReactレンダリングプロセスからのDiffアルゴリズムの分析に関する簡単な説明
  • React diffアルゴリズムの実装例

<<:  Ubuntu 18.04 が VMware 仮想マシンでネットワークに接続できない問題の解決策

>>:  MySQL ステートメントコメントの紹介

推薦する

Mybatisの各SQL文の実行時間の統計

背景最近、面接でデータベース トランザクションについてよく質問されます。通常は、@Transacti...

MySQL の count 関数の正しい使い方の詳細な説明

1. 説明MySQLでは、テーブル内の行の総数を取得する必要がある場合、通常は次の文を使用します。 ...

jsは画像切り取り機能を実現する

この記事の例では、画像の切り取りを実現するためのjsの具体的なコードを参考までに共有しています。具体...

CentOS8 で MySQL 8.0 をインストールしてデプロイする方法

MySQL 8 の公式バージョン 8.0.11 がリリースされました。公式発表によると、MySQL ...

HTMLはa要素hrefのURLリンクを自動的に更新したり新しいウィンドウを開いたりする機能を実装する

場合によっては、次のような機能を実装したいことがあります。リンクをクリックします。リンクがブラウザで...

Windows 10 での MySQL 5.7.21 winx64 のインストールと設定方法のグラフィック チュートリアル

mysql 5.7.21 winx64 のインストールと設定方法: MySQLのコミュニティバージョ...

echarts ワードクラウドチャートを使用した Vue の実践記録

echartsワードクラウドはechartsの拡張版ですhttps://echarts.apache...

DockerコンテナでJupyterノートブックを設定する方法

Jupyter ノートブックは、主に Python コードの記述、より具体的にはディープラーニング開...

Linux システムでキャッシュをクリアする方法の概要

1) キャッシュメカニズムの紹介Linux システムでは、ファイルシステムのパフォーマンスを向上させ...

VMware Workstation Pro は Win10 ピュア バージョンのオペレーティング システムをインストールします

この記事では、VMware Workstation Pro で Win10 オペレーティング システ...

Vue2とVue3のライフサイクルの比較の詳細な理解

目次サイクル比較使用法要約するサイクル比較ヴュー2ヴュー3作成前設定作成された設定マウント前マウント...

Linux md5sumコマンドの使い方

01. コマンドの概要md5sum - MD5検証コードを計算して検証するmd5sum コマンドは、...

スケジュールされた時間に古いジャンクファイルを自動的に削除する Linux 用の Autotrash ツール

Autotrash は、古い削除済みファイルを消去するプロセスを自動化するコマンド ライン プログラ...

ミニプログラムにより、製品属性の選択や仕様の選択が可能

この記事では、ミニプログラムで製品属性選択または仕様選択を実装するための具体的なコードを参考までに共...

Raspberry Pi 4 に Ubuntu 19.10 をインストールするための詳細なチュートリアル

以前、raspbian で実行したときに opencv の一部の依存関係をパッケージ化できず、一部の...