React の Diff アルゴリズムは、調整アルゴリズムとも呼ばれます。対応する関数は 以前、Diff アルゴリズムを 2 つの Fiber ツリーの比較として説明する記事をいくつか読んだことがあります。これは誤りです。実際の Diff プロセスは、既存の Fiber ノードのセットと Node Diff は次の 2 つのタイプに分かれています。
次の React バージョンは 17.0.1、コード ファイルは ReactChildFiber.old.js です。 単一ノード差分単一ノードの Diff は比較的単純です。キーとタイプが同じ場合にのみノードを再利用しようとし、そうでない場合は新しいノードを返します。 ほとんどの場合、単一のノードにキーを割り当てないので、デフォルトでは null になり、同じになります。 単一要素を調整する// 単一ノード比較関数 reconcileSingleElement( returnFiber: ファイバー、 currentFirstChild: ファイバー | null、 要素: ReactElement、 レーン: レーン、 ): ファイバー { // 新しい reactElement のキー 定数キー = 要素.キー; //現在の子ファイバーノード let child = currentFirstChild; while (child !== null) { // キーが同じ場合のみ比較 if (child.key === key) { スイッチ(子タグ){ // ... デフォルト: { // 現在のファイバーと reactElement が同じタイプの場合、if (child.elementType === element.type) { // 同じレベルの他のノードを削除します deleteRemainingChildren(returnFiber, child.sibling); // 現在の子ファイバーを再利用します const existing = useFiber(child, element.props); existing.ref = coerceRef(returnFiber、child、element); 既存の.return = returnFiber; // 再利用可能な子ファイバーを返す 既存のものを返します。 } 壊す; } } // 一致しないノードを削除します deleteRemainingChildren(returnFiber, child); 壊す; } それ以外 { // キーが異なる場合はノードを直接削除します deleteChild(returnFiber, child); } 子 = 子.兄弟; } // 新しいファイバーノード const created = createFiberFromElement(element, returnFiber.mode, lanes); created.ref = coerceRef(returnFiber、currentFirstChild、要素); 作成された戻り値 = returnFiber; 戻り値が作成されました。 } マルチノード差分ソース コードは、複数のノードを配列ノードと反復可能なノードに分割します。 if (isArray(newChild)) { reconcileChildrenArray() を返す リターンファイバー、 現在の最初の子、 新しい子、 車線、 ); } (getIteratorFn(新しい子))の場合{ reconcileChildrenIterator() を返す リターンファイバー、 現在の最初の子、 新しい子、 車線、 ); } 対応する Diff 関数は このコードセクションは比較的長いですが、ロジックは非常に明確であり、分割線から 2 ラウンドのトラバースに分割されています。
最初のトラバーサル ラウンドは、キーと順序が同じ場合のみに行われます。これらのキーに対応するノードの位置は変更されていないため、更新操作のみが必要です。トラバーサルで異なるキーに遭遇すると、ループから抜け出す必要があります。 // 古いノード <li key="0"/> <li キー="1"/> <li キー="2"/> // 新しいノード <li key="0"/> <li キー="1"/> <li キー="5"/> // key="5" は異なるため、トラバーサルから抜ける // トラバーサル ノードの最初のラウンド <li key="0"/> <li キー="1"/> // <li key="2"/> と <li key="5"/> は、2 回目の走査と比較で残ります。 最初のトラバースラウンドの後には、2 つの状況があります。
2 回目の走査は、異なるキーまたは異なる順序に対して行われます。考えられる状況は次のとおりです。 // 古いノード <li key="0"/> <li キー="1"/> <li キー="2"/> // 新しいノード <li key="0"/> <li キー="2"/> <li キー="1"/> // 2 回目の走査では、2 つのノード <li key="2"/> と <li key="1"/> を比較します。 2 回目のトラバーサルは少し複雑になりますが、これについては後で詳しく説明します。 詳細なコードは以下のとおりです。 調整子配列関数 reconcileChildrenArray( returnFiber: ファイバー、 currentFirstChild: ファイバー | null、 新しい子: 配列<*>, レーン: レーン、 ): ファイバー | null { // 関数によって返されるファイバー ノード let resultsingFirstChild: Fiber | null = null; previousNewFiber: Fiber | null = null; とします。 // oldFiber はリンク リストです。let oldFiber = currentFirstChild; // ノードが移動されたかどうかを判断するために使用されます。let lastPlacedIndex = 0; newIdx = 0 とします。 nextOldFiber を null にします。 // 最初の走査では、同じキーを持つノードのみが走査されます for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) { (oldFiber.index > newIdx) の場合 { 次の古いファイバー = 古いファイバー; 古いファイバー = null; } それ以外 { // 古いファイバー ノードがループされるたびに、次のループのファイバー ノードである兄弟要素を指します。 nextOldFiber = oldFiber.sibling; } // キーが同じ場合はファイバーノードを返します。キーが異なる場合は null を返します。 // 型が同じ場合はノードを再利用し、そうでない場合は新しいノードを返します。const newFiber = updateSlot( リターンファイバー、 古いファイバー、 新しい子供[newIdx]、 車線、 ); // newFiber は null で、キーが異なることを示し、ループは終了します if (newFiber === null) { (古いファイバーがnullの場合){ 古いファイバー = 次の古いファイバー; } 壊す; } // newFiber.alternate が null の場合、それは新しいノードであり、異なるタイプの新しいファイバーノードが作成されたことを示します。if (oldFiber && newFiber.alternate === null) { // oldFiber タグを削除する必要があります deleteChild(returnFiber, oldFiber); } // ノードを配置し、lastPlacedIndexを更新します lastPlacedIndex = placeChild(newFiber、lastPlacedIndex、newIdx); // 新しいファイバーノードチェーンを形成する if (previousNewFiber === null) { 結果として得られるFirstChild = newFiber; } それ以外 { 前のNewFiber.sibling = newFiber; } 前のNewFiber = 新しいFiber; 古いファイバー = 次の古いファイバー; } /* 最初の走査ラウンドの後、新しいノードの数は古いノードの数よりも少なくなります。newChildren は走査され、残りのファイバーノードは削除されます。考えられる状況は次のとおりです⬇️ <li key="0"/> の前 <li キー="1"/> <li キー="2"/> 新規 <li key="0"/> <li キー="1"/> <li key="2"/> は削除されます*/ (newIdx === newChildren.length) の場合 { 残りの子供を削除します(returnFiber、oldFiber); 結果のFirstChildを返します。 } /* 最初のトラバースラウンドの後、新しいノードの数が古いノードの数より多くなっています。OldFiber がトラバースされました。考えられる状況は次のとおりです⬇️ <li key="0"/> の前 <li キー="1"/> 新規 <li key="0"/> <li キー="1"/> <li キー="2"/> 新しい <li key="2"/> が追加されます。このセクションは、新しいノードの挿入ロジックです*/ (古いファイバーがnullの場合){ (; newIdx < newChildren.length; newIdx++) { const newFiber = createChild(returnFiber, newChildren[newIdx], レーン); (newFiber === null)の場合{ 続く; } lastPlacedIndex = placeChild(newFiber、lastPlacedIndex、newIdx); // 新しいファイバーノードチェーンを形成する if (previousNewFiber === null) { 結果として得られるFirstChild = newFiber; } それ以外 { 前のNewFiber.sibling = newFiber; } 前のNewFiber = 新しいFiber; } 結果のFirstChildを返します。 } // --------------------------------------------------------------------- // 残りの oldFiber を使用して、key->fiber ノード マップを作成し、対応する古いファイバー ノードを key で取得できるようにします。const existingChildren = mapRemainingChildren(returnFiber, oldFiber); // 2 回目のトラバーサル。残りのノードのトラバーサルを続けます。これらのノードは移動または削除する必要がある可能性があります。for (; newIdx < newChildren.length; newIdx++) { // マップからキーに対応する古いノードを取得し、更新された新しいノードを返します。const newFiber = updateFromMap( 既存の子供、 リターンファイバー、 新しいIDx、 新しい子供[newIdx]、 車線、 ); newFiber !== null の場合 { // 新しいノードを再利用し、古いノードをマップから削除します。対応する状況は位置の変更である可能性があります if (newFiber.alternate !== null) { // 再利用されたノードはマップから削除する必要があります。マップ内の残りのノードは削除としてマークされるためです existingChildren.delete( newFiber.key === null ? newIdx : newFiber.key, ); } // ノードを配置し、移動するかどうかを決定します lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); if (以前の新しいファイバー === null) { 結果として得られるFirstChild = newFiber; } それ以外 { 前のNewFiber.sibling = newFiber; } 前のNewFiber = 新しいFiber; } } // 残りの不要なノードを削除します existingChildren.forEach(child => deleteChild(returnFiber, child)); 結果のFirstChildを返します。 } 最初のトラバーサルは比較的簡単に理解できます。ここでは、2 回目のトラバーサルを詳細に分析します。2 回目のトラバーサルでは、再利用を移動する必要があるかどうかという疑問が生じるためです。 2 回目のトラバーサルでは、まず残りの 次のトラバーサルでは、新しいノードをトラバーサル オブジェクトとして使用します。トラバーサルでは、新しいノードのキーを使用してマップから古いノードを取得し、再利用できるかどうかを比較するたびに、新しいノードのキーを使用します。対応する関数は ノードに ノードが移動したかどうかを判断するにはどうすればよいですか?ここには、ノードが移動したかどうかを判断するための変数 再利用されたノードの 関数 placeChild( newFiber: ファイバー、 lastPlacedIndex: 数値、 新しいインデックス: 数値、 ): 番号 { 新しいファイバーのインデックスを作成します。 現在の定数を newFiber.alternate に設定します。 (現在の値 !== null) の場合 { const oldIndex = 現在のインデックス; (古いインデックスが最後の配置インデックスより小さい場合){ // ノードの移動 newFiber.flags = Placement; lastPlacedIndex を返します。 } それ以外 { // ノードの位置は変更されていません return oldIndex; } } それ以外 { // 新しいノードを挿入します newFiber.flags = Placement; lastPlacedIndex を返します。 } } 例えば: // 古いabcd // 新しいacbd abcd はすべてキー値です。 最初の走査ラウンドの後、残りのノードを比較する必要があります。 // 古いbcd // 新しい CBD ノード a は最初のラウンドで再利用され、placeChild が呼び出されました。この時点で、lastPlacedIndex 値は 0 です。 2 回目のトラバーサルに入ると、新しいノードは依然としてトラバーサル オブジェクトです。 c => 古いノードに存在し、再利用できます。古いノードのインデックスは 2 です。2 > lastPlacedIndex(0)。移動する必要はありません。lastPlacedIndex に 2 を割り当てます。 b => は古いノードに存在し、再利用できます。古いノードでのインデックスは 1 で、1 < lastPlacedIndex(2) です。移動して Placement でマークする必要があります。 d => 古いノードに存在し、再利用できます。古いノードのインデックスは3で、3 > lastPlacedIndex(2)なので、移動する必要はありません。 この例から、React では、移動する必要のない右側のノードが参照として使用され、移動する必要があるノードはすべて左から右に均一に移動されることがわかります。 後続のレイアウト ステージでは、 要約するReact の Diff アルゴリズムのコア コードはそれほど長くありませんが、キーの導入により複雑度が O(n3) から O(n) に巧妙に変更されます。 プログラマー間の社内競争が激しいので、ソースコードを学ばなければなりません。 上記は、React diffアルゴリズムのソースコード分析の詳細な内容です。React diffアルゴリズムの詳細については、123WORDPRESS.COMの他の関連記事に注目してください。 以下もご興味があるかもしれません:
|
<<: Linuxでのaliasコマンドの使い方の詳細な説明
>>: 「MySQL サービスを開始できません エラー 1069」を解決する方法
最近、MySQL を使っています。Linux での mysql-installation という記事...
以下は、Flex レイアウトを使用した棒グラフです。 HTML: <div class=&qu...
目次1 バージョンと計画1.1 バージョン情報: 1.2 クラスター計画2. 展開1. ファイアウォ...
HTMLテキスト書式タグ 標簽 描述 <b> 定義粗體文本 <em> 呈現...
目次概要setTimeout() の確認スリープ関数の書き方シンプルな選択ループで実行されますか?要...
vue+element UI は Excel データをエクスポートするためのパブリック関数をカプセル...
実際、Apacheクラスタを構築するのは難しくありません。私もインターネットで情報を見つけて自分で設...
目次1. トラバーサルクラス1. 各2. 地図3. すべての4. いくつか5. フィルター6. 減ら...
方法:実際のプロジェクトを例に挙げてみましょう。 .lk-ツールバー{ .el-入力{ 幅: 169...
背景Dockerでは、同じイメージを使用して4つのコンテナを作成します。ネットワークはブリッジモード...
一時テーブルの概要一時テーブルとは: MySQL は中間結果セットを保存するために使用されます。一時...
この記事では、ネイティブ JS で実装されたブラインドの特殊効果を紹介します。効果は次のとおりです。...
1. 命名規則1. データベース名、テーブル名、フィールド名には小文字を使用し、アンダースコアで区切...
テーブルを美しくするために、行ごとに異なる境界線の色を設定できます。基本的な構文<TR 境界線...
HTML Police がコードを調べて意味のないタグをすべて見つけ出すので、注意を払う必要がありま...