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 20 の高性能アーキテクチャ設計原則 (収集する価値あり)

オープンソース データベース アーキテクチャの設計原則01. 技術の選択最も使い慣れていて、最大限に...

React スキャフォールディングのパスエイリアスを設定する方法

この記事を書いている時点でのReactのバージョンは16.13.1です1 npm run eject...

JSで実施された機雷掃海プロジェクトの概要

この記事では、JS掃海プロジェクトの概要を参考までに紹介します。具体的な内容は次のとおりです。プロジ...

シンプルなショッピングカート機能を実現するjs

この記事の例では、簡単なショッピングカート機能を実現するためのjsの具体的なコードを参考までに共有し...

Logrotate は 2 時間ごとに Catalina.out ログローテーションを実装します

1. Logrotateツールの紹介Logrotate はログファイル管理ツールです。Linux に...

HTML CSS の 3 つの一般的なスタイル セレクター

1: タグセレクタータグセレクターはすべてのタグに使用されます。ここでは p を例に挙げます。つまり...

vue.js ベースの QQ チャット ルーム

目次導入効果のデモンストレーションは次のとおりです。 MChat コンポーネントのレンダリング: I...

ウェブデザイナーのウェブデザイン学習経験とスキルのまとめ

会社の影響力が拡大し、製品が改良され続けるにつれて、関連するイメージデザインもそれに追いつき、徐々に...

CSS3で実装された炎のアニメーション

成果を達成する実装コードhtml <div class="コンテナ">...

JS を使って CSS3 で丸い角を実装する方法

IE で CSS3 を使用して角を丸くする方法を探していたときに、例を見つけました。まだテストして...

CentOS 7.3 で Nginx 仮想ホストを設定する方法

実験環境最小限にインストールされた CentOS 7.3 仮想マシン基本環境を構成する1. ngin...

Dockerカスタムネットワーク実装

目次1. コンテナ相互接続を実現するためにネットワークをカスタマイズする2. ネットワーク接続1. ...

ウェブページのコピー防止機能の実装方法(クラッキング手法付き)

ソース ファイルを右クリックすると、次のコードが見つかります。 1. CSSを使用してFirefox...

Linux 仮想マシンの IP アドレスを変更し、ゲートウェイを確認し、ネットワーク環境を構成する方法に関するチュートリアル

仮想マシンの IP アドレスを変更します。 次のインターフェイスに入り、サブネット IP を直接変更...

React.Childrenの詳しい使い方

目次1. React.Children.map 2. React.Children.forEach ...