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 ステートメントコメントの紹介

推薦する

MySQL 5.7.17 zip パッケージ バージョンを Windows 10 にインストールするチュートリアル

mysql5.7.17のインストールチュートリアルを参考までに共有します。具体的な内容は次のとおりで...

MySQL インデックスの最適化: ページング探索の詳細な紹介

目次MySQL インデックス最適化ページングの調査ケース1ケース2 MySQL インデックス最適化ペ...

div を下から上にスライドさせる CSS3 の例

1. まず、CSS3 のターゲット セレクターを使用し、a タグを使用して id セレクターを指定し...

Linux の crw、brw、lrw などのファイル属性は何ですか?

ファイルとは何ですか?すべてのファイルは実際には文字列のストリームですが、適切な解析方法を使用すると...

2つのNode.jsプロセスがどのように通信するかの詳細な説明

目次序文異なるコンピュータ上の 2 つの Node.js プロセス間の通信TCPソケットの使用HTT...

MySQLへのJava接続の基礎となるカプセル化の詳細な説明

この記事では、Java接続MySQLの基礎となるカプセル化コードを参考までに紹介します。具体的な内容...

背景画像の配置におけるbackground-position属性の自己理解

最近、プロジェクトではラベルやボタンなどの断片的な画像をたくさん使用する必要があります。また、CSS...

MySql ページングで limit+order by を使用する場合のデータ重複の解決策

目次まとめ問題の説明問題を分析する問題を解決するまとめ複雑な知識をシンプルに説明できることは重要です...

a href=# と a href=javascript:void(0) の違いの詳細な説明

a href="#"> リンクをクリックすると、ページがページ上部までスク...

Vue大画面表示適応方法

この記事では、vueの大画面表示適応の具体的なコードを参考までに紹介します。具体的な内容は以下のとお...

MySQL が「operate_time」エラーのデフォルト値が無効であると報告する問題を解決する

データベースでcreate tableステートメントを実行する テーブル `sys_acl` を作成...

VueコンポーネントライブラリElementUIはテーブルリストのページング効果を実現します

ElementUIはテーブルリストのページング効果のチュートリアルを実装しています。参考までに。具体...

レスポンシブ Web をデザインするにはどうすればいいですか?レスポンシブウェブデザインのメリットとデメリット

最近レスポンシブ デザインについて学んでいて、これについていくつか整理してみました。写真の一部はイン...

MySQL 基本チュートリアル パート 1 MySQL5.7.18 のインストールと接続チュートリアル

この記事から、MySQL を紹介し学習するための新しい一連の記事がスタートします。なぜ MySQL ...

MySQL クロスデータベーストランザクション XA 操作の例

この記事では、例を使用して、MySQL のデータベース間トランザクション XA 操作について説明しま...