概要diff アルゴリズムは Vue のコアコンテンツと言えます。私はこれまで開発で Vue を使用したことがあり、具体的なコアコンテンツについてはあまり知りませんでした。最近、この分野のコンテンツを偶然読みました。Vue2.0 の diff アルゴリズムの実装についてお話ししましょう。具体的には、実装されているいくつかの機能から分析していきます。 仮想ドム仮想 DOM は実際の DOM のデータを抽出し、オブジェクトの形式でツリー構造をシミュレートします。 例えば、以下は実際のDOMです。 <div> <p>1234</p> <div> <span>1111</span> </div> </div> 実際のDOMに従って生成された仮想DOMは次のようになります var Vnode = { タグ: 'div', 子供たち: [ { タグ: 'p', テキスト: '1234' }, { タグ: 'div', 子供たち:[ { タグ: 'span', テキスト: '1111' } ] } ] } 原理diff の原理は、現在の実際の DOM が仮想 DOM を生成することです。仮想 DOM 内のノードのデータが変更されると、新しい Vnode が生成されます。次に、この Vnode が古い oldVnode と比較されます。違いがある場合は、実際の DOM 上で直接変更されます。 実装プロセスdiff アルゴリズムの実装プロセスの中核は patch です。patchVnode、sameVnode、updateChildren メソッドは注目に値します。以下、順に説明します。 パッチ方式パッチのコアロジックは、2つのVnodeノードを比較し、その違いをビューに更新することです。比較方法は、各レベルをループするのではなく、ピア比較です。比較後に違いが見つかった場合、これらの違いはビューに更新されます。比較方法の例は次のとおりです。 sameVnode関数sameVnode の機能は、2 つのノードが同じかどうかを判断することです。判断の基準は、キー値、タグ、isCommit、入力タイプが一貫しているかどうかなどです。この方法にはいくつかの欠陥があります。v-for の下のキー値がインデックスを使用する状況に直面した場合、再利用可能なノードとして判断される可能性もあります。 インデックスをキー値として使用しないことをお勧めします。 patchVnode関数// いくつかのパラメータを渡します。oldVnode は古いノード、vnode は新しいノード、readOnly は読み取り専用ノードかどうかを表します。関数 patchVnode ( 古いVノード、 vノード、 挿入されたVnodeQueue、 所有者配列、 索引、 削除のみ ){ if (oldVnode === vnode) { //古いノードと新しいノードが一致している場合は比較は不要です。 return return } if (isDef(vnode.elm) && isDef(ownerArray)) { // 再利用された vnode を複製する vnode = ownerArray[インデックス] = cloneVNode(vnode) } const elm = vnode.elm = 古いVnode.elm if (isTrue(oldVnode.isAsyncPlaceholder)) { (vnode.asyncFactory.resolved がisDefである)場合 水和物(古いVnode.elm、vnode、挿入されたVnodeQueue) } それ以外 { vnode.isAsyncPlaceholder = true } 戻る } // 静的ツリーの要素を再利用します // vnode が複製された場合にのみこれを実行します // 新しいノードが複製されていない場合は、レンダリング関数が複製されたことを意味します // hot-reload-api を介してリセットし、適切な再レンダリングを行う必要があります。 if (isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === 古いVnode.key && (vnode.isCloned が True の場合 || vnode.isOnce が True の場合) ){ vnode.componentInstance = 古いVnode.componentInstance 戻る } 私は 定数データ = vnode.data isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch) の場合 i(古いVノード、vノード) } 定数 oldCh = oldVnode.children 定数ch = vnode.children if (isDef(data) && isPatchable(vnode)) { (i = 0; i < cbs.update.length; ++i) の場合、cbs.update[i](oldVnode, vnode) if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode) } if (isUndef(vnode.text)) { isDef(oldCh) と isDef(ch) のどちらが正しいか oldCh !== ch の場合、elm の子を更新します。oldCh の子は ch の子であり、insertedVnodeQueue の子は ch の子であり、removeOnly の子は elm の子です。 } そうでなければ (isDef(ch)) { process.env.NODE_ENV !== 'production' の場合 { 重複キーのチェック(ch) } if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '') addVnodes(elm, null, ch, 0, ch.length - 1, 挿入されたVnodeQueue) } そうでなければ (isDef(oldCh)) { Vnodesを削除します(oldCh, 0, oldCh.length - 1) } そうでない場合 (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, '') } } そうでない場合 (oldVnode.text !== vnode.text) { nodeOps.setTextContent(elm、vnode.text) は、 } if (isDef(データ)) { if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode) } } 具体的な実装ロジックは次のとおりです。
updateChildren 関数updateChildren は、その名前が示すように、子ノードを更新するためのメソッドです。上記の patchVnode メソッドから、新しいノードと古いノードの両方に子ノードがある場合にこのメソッドが実行されることがわかります。実装ロジックを見てみましょう。これに似た例の図もいくつかあるかもしれません。まずはコードを見てみましょう。 関数 updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) { oldStartIdx = 0 とする newStartIdx = 0 とします oldEndIdx = oldCh.length - 1 とします。 oldStartVnode = oldCh[0]とする oldEndVnode = oldCh[oldEndIdx]とする newEndIdx = newCh.length - 1 とします。 newStartVnode = newCh[0]とする newEndVnode = newCh[newEndIdx]とします。 oldKeyToIdx、idxInOld、vnodeToMove、refElm を使用します。 // removeOnly は <transition-group> でのみ使用される特別なフラグです // 削除された要素が正しい相対位置に留まるようにする // 退出遷移中 const canMove = !removeOnly process.env.NODE_ENV !== 'production' の場合 { 重複キーのチェック(newCh) } (古い開始Idx <= 古い終了Idx && 新しい開始Idx <= 新しい終了Idx) { if (isUndef(oldStartVnode)) { oldStartVnode = oldCh[++oldStartIdx] // Vnodeが左に移動されました } そうでない場合 (isUndef(oldEndVnode)) { oldEndVnode = oldCh[--oldEndIdx] } そうでない場合 (sameVnode(oldStartVnode, newStartVnode)) { パッチVnode(古い開始Vnode、新しい開始Vnode、挿入されたVnodeQueue、新しいCh、新しい開始Idx) 古い開始Vノード = 古いCh[++古い開始Idx] 新しい開始Vノード = 新しいCh[++新しい開始Idx] } そうでない場合 (sameVnode(oldEndVnode, newEndVnode)) { パッチVnode(古い終了Vnode、新しい終了Vnode、挿入されたVnodeQueue、新しいCh、新しい終了Idx) oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnodeが右に移動 パッチVnode(古い開始Vnode、新しい終了Vnode、挿入されたVnodeQueue、新しいCh、新しい終了Idx) canMove && nodeOps.insertBefore(親Elm、oldStartVnode.elm、nodeOps.nextSibling(oldEndVnode.elm)) 古い開始Vノード = 古いCh[++古い開始Idx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnodeが左に移動 パッチVnode(古い終了Vnode、新しい開始Vnode、挿入されたVnodeQueue、新しいCh、新しい開始Idx) canMove && nodeOps.insertBefore(親Elm、古い終了Vnode.elm、古い開始Vnode.elm) oldEndVnode = oldCh[--oldEndIdx] 新しい開始Vノード = 新しいCh[++新しい開始Idx] } それ以外 { if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) idxInOld = isDef(newStartVnode.key) ? 古いキーからIdx[新しい開始Vノード.キー] : findIdxInOld(新しい開始ノード、古いCh、古い開始Idx、古い終了Idx) if (isUndef(idxInOld)) { // 新しい要素 createElm(newStartVnode、insertedVnodeQueue、parentElm、oldStartVnode.elm、false、newCh、newStartIdx) } それ以外 { vnodeToMove = 古いCh[idxInOld] 同じVnode(vnodeToMove, newStartVnode)の場合 パッチVnode(vnodeToMove、newStartVnode、insertedVnodeQueue、newCh、newStartIdx) oldCh[idxInOld] = 未定義 canMove && nodeOps.insertBefore(親Elm、vnodeToMove.elm、oldStartVnode.elm) } それ以外 { // 同じキーだが要素が異なる。新しい要素として扱う createElm(newStartVnode、insertedVnodeQueue、parentElm、oldStartVnode.elm、false、newCh、newStartIdx) } } 新しい開始Vノード = 新しいCh[++新しい開始Idx] } } (古い開始ID>古い終了ID)の場合{ refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm Vnodesを追加します(parentElm、refElm、newCh、newStartIdx、newEndIdx、insertedVnodeQueue) } そうでない場合 (newStartIdx > newEndIdx) { Vnodes を削除します (古い Ch、古い開始 ID、古い終了 ID) } } ここでは、まず、oldStartIdx (古いノードの最初のインデックス)、oldEndIdx (古いノードの最後のインデックス)、oldStartVnode (古いノードの最初の要素)、oldEndVnode (古いノードの最後の要素) などのいくつかのパラメータを定義します。同様に、newStartIdx と他の 4 つの項目は、新しいノードの最初のインデックスなどです。 whileループ内の操作を見てください。これもコアコンテンツです。 同じノードであると判断した後、ノードはpatchVnodeメソッドを続行する必要がある。
newStartVnode と同じキーを持つ Vnode が見つかった場合、その Vnode は vnodeToMove と名付けられ、次に vnodeToMove が newStartVnode と比較されます。同じ場合は、両方が再度パッチされます。removeOnly が false の場合、newStartVnode と同じキーを持つ vnodeToMove.elm という名前の Vnode が、oldStartVnode.elm の前に移動されます。キー値は同じでもノードが異なる場合は、新しいノードが作成されます。 While ループの後、新しいノード配列または古いノード配列にまだノードが残っている場合は、具体的な状況に応じてそれらを削除または追加します。 oldStartIdx > oldEndIdxの場合、oldChが最初に走査され、冗長な新しいノードが存在することを意味します。新しいノードを追加します newStartIdx > newEndIdx の場合、新しいノードが最初に走査され、古いノードがまだ残っているため、残りのノードを削除します。 下の例の写真を見てみましょう 元のノード (oldVnode は古いノード、Vnode は新しいノード、diff は diff アルゴリズムの後に生成されたノード配列です) 最初にループすると、古い末尾要素が新しい最初の要素と同じであることがわかるので、古い末尾要素 D は古い最初のインデックスの先頭、つまり A の前に移動されます。同時に、古い末尾インデックスは左に移動し、新しい最初のインデックスは右に移動します。 2 回目のループでは、新しい最初の要素は古い最初の要素と同じになります。このとき、2 つの要素の位置は変化せず、新しい最初のインデックスと古い最初のインデックスは同時に右に移動します。 3 番目のループでは、古い要素に現在の要素と同じノードが存在しないことが検出されるため、新しいノードが追加され、古い最初の要素の前に F が配置されます。同様に、4 番目のループも同じです。2 つのループの後、新しいサンプル グラフが生成されます。 5 番目のサイクルは 2 番目のサイクルと同じです。 6番目のループでは、newStartIdxが再び右に移動します 7. 最後の移動の後、newStartIdx > newEndIdxとなり、whileループは終了しました。これは、newChが最初に走査され、oldChにはまだ余分なノードがあり、それらは直接削除されることを証明しています。そのため、最後のノードは 上記は、diffアルゴリズムとdiffアルゴリズムの実装プロセスに関連するいくつかの機能です。 結論差分アルゴリズムは仮想 DOM の中核部分です。同じレイヤーを比較し、新しいノードと古いノードを比較して変更された部分を実際の DOM に更新します。 具体的な実装方法は、patch、patchVnode、updateChildrenです。 パッチの核となるのは、新しいノードは存在するが古いノードは存在しない場合はそれを追加すること、古いノードは存在するが新しいノードは存在しない場合はそれを削除すること、両方が存在する場合はそれらが同じかどうかを判断し、同じ場合は比較の次のステップのために patchVnode を呼び出すことです。 patchVnode の核となるのは、新しいノードと古いノードがコメントやテキスト ノードではなく、新しいノードに子ノードがあるが古いノードにはない場合は子ノードを追加する、新しいノードに子ノードがなく古いノードに子ノードがある場合は古いノードの下の子ノードを削除する、両方に子ノードがある場合は updateChildren メソッドを呼び出す、というものです。 これは、Vue2.0 の diff アルゴリズムの予備的な説明にすぎません。より深い原理と、Vue3.0 の diff アルゴリズムに変更があるかどうかについては、まだ解明されていません。 Vue の diff アルゴリズムに関するこの記事はこれで終わりです。Vue の diff アルゴリズムの詳細については、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。 以下もご興味があるかもしれません:
|
<<: MySQL 8.0.20 のインストールと設定方法のグラフィックチュートリアル
>>: Linux での MongoDB のインストールと設定のチュートリアル
#include <linux/moduleparam.h> 1. モジュールパラメータ...
年、月、週、日グループによる MySQL クエリ1. 学年別検索 SELECT DATE_FORMA...
1. Dockerコンテナを起動する以下のコマンドを使用して新しい Docker コンテナを起動しま...
a : ハイパーリンクの開始位置または宛先位置を示します。頭字語: 単語の最初の文字からなる略語を示...
大規模な開発に Docker を使用する場合でも、クリーンアップ戦略がなければ、ディスクがすぐにいっ...
MySQL InnoDB 監視 (システム層、データベース層) MySQL の監視に関しては、My...
目次Linux の MariaDB データベースについて1. データベースとは何ですか? 2. デー...
目次実装のアイデア:ステップ 1: TabBar と TabBarItem のコンポーネント カプセ...
私は最近、最も安い Tencent クラウド サーバーを購入しました。これは主に、Web テクノロジ...
mysql データベースには student テーブルがあり、その構造は次のとおりです。 Oracl...
この記事の例では、Appleの電卓を実装するためのJSの具体的なコードを参考までに共有しています。具...
目次ローカルミックスイングローバル ミックスイン要約するローカルミックスイン <テンプレート&...
序文データ型変換とは何ですか?フォームまたはプロンプトを使用して取得されるデフォルトのデータ型は文字...
よく聞かれる答えは、列に NULL 値を使用するとインデックスが無効になるというものですが、実際にテ...
ConcurrentHashMapを学習しましたが、どのように適用すればよいかわかりませんか? To...