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

推薦する

この記事は、JQueryの基本的な操作を理解し、始めるのに役立ちます。

目次1. Jquery を使用する手順: (1)jsライブラリをインポートする(2)ページ読み込みイ...

ローカルアイデアアクティベーションサーバーの構築に関する詳細なチュートリアル

序文ブロガーはアイデアIDEを使用しています。アイデア公式が最近サードパーティのアクティベーションサ...

Ubuntu 18.04 に Anaconda3 をインストールするための詳細なチュートリアル

Anaconda は、conda、Python、およびそれらの依存関係など、180 を超える科学パッ...

mysql5.6 の無効な utf8 設定の問題を解決する

mysql5.6 のグリーン バージョンを解凍すると、my-default.ini ファイルが作成さ...

Vueはキャンバスを使用して画像圧縮アップロードを実現します

この記事では、キャンバスを使用して画像圧縮アップロードを実現するVueの具体的なコードを参考までに共...

Ubuntu 20.04でルートアカウントを有効にする方法

Ubuntu 20.04 をインストールした後、デフォルトでは root アカウントのログイン権限が...

nginx リクエスト ヘッダー データ読み取りプロセスの詳細な説明

前回の記事では、nginx がリクエスト ラインのデータを読み取って、リクエスト ラインを解析する方...

Vueプロジェクトをパッケージ化してリリースする手順

目次1. 開発環境から本番環境への移行2. 統一されたリクエストパスを設定する3. パッケージ化コマ...

Vue3はフロントエンドのログを出力するためにaxiosインターセプターを使用する

目次1. はじめに2. axiosインターセプターを使用してフロントエンドログを出力する1. はじめ...

MySQL クラスター化インデックスのページ分割原理の分析例

この記事では、MySQL クラスター化インデックスのページ分割を例を使って説明します。ご参考までに、...

Html+CSS 描画三角形アイコン

まずはレンダリングを見てみましょう: XML/HTML コードコンテンツをクリップボードにコピー&l...

MySQLインデックスの作成について知っておくべきこと

目次序文: 1. インデックスメソッドを作成する2. インデックスを作成するために必要な権限序文: ...

MySQL でのデータベース間クエリの例

序文MySQL では、クロスデータベース クエリは主に 2 つの状況に分けられます。1 つは同じサー...

Docker で Tomcat を使用して Web アプリケーションを迅速にデプロイする方法の例

Docker の基本的な操作を学習した後、コンテナにいくつかの基本的なアプリケーションをデプロイして...

Mac で MySQL 8.0.22 のパスワードを取得する方法

Mac 最新バージョンの MySQL 8.0.22 パスワード回復問題の説明:昨日、突然、Macで最...