またのような、ない詳細な説明の内容の一部に、VUEの差分プロセスを話すが、それはちょうどそれらのいくつか(コンペアモード)について話しました感じている他の記事を読みます
- 試合が行われた場合は
patchVnode
何をしているのですか?なぜ一部にはない、DOM操作を踏襲することがありますか? - diffの中に、どのように特定の移動のポインタ?そして、どの部分が変更されましたか?
insertedVnodeQueue
用途は何ですか?なぜ、とされて?- その後も直接oldChildrenモバイル事業のこの部分の記事の多くは、長い時間のための困惑が、oldChildrenの動きが起こるの?だから、最終的に誰がそれを動かすために起こったのか?
我々はいくつかの単純な概念とプロセスは、あなたがこのパッチVUEのソースを持っているという希望のために何が最善か、もちろん事前に説明する必要がどこのコアを開始する前に、デフの詳細な過程を知ることができるようにここでは、直接順に、差分を話し始めていませんいくつかの理解の一部。
いくつかの概念
コアは、プロセスdiffがあるので、まずこれらはまだコメント欄にメッセージを残すことができます質問がある場合は簡単に、説明コア概念に関連するDIFFます。
1. vノード
仮想DOM - だけでも、VUEの特徴の一つである、オブジェクト、の本当のDOMの説明を意味しています。ネイティブ複雑すぎるのDOM構造、時間のノード情報を取得し、理解することが所望される場合、VUE対応する、複雑な操作のDOMを必要としないが最初に分析されるので、オブジェクトを記述していると(差分比較、すなわちvノードをコントラスト)、次いで反応実際のDOMへ。
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
// strictly internal
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
asyncFactory: Function | void; // async component factory function
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
functionalContext: Component | void; // real context vm for functional nodes
functionalOptions: ?ComponentOptions; // for SSR caching
functionalScopeId: ?string; // functioanl scope id support
constructor () {
...
}
}
复制代码
後には、いくつかの特性に関連されることに注意してください:
children
そして、parent
実際のDOMの対応する階層である、このを通じてvノード間の階層関係を確立しますtext
対応するvノードが子供を持つ文書ノードであることを証明する価値がある場合には、相互に排他的な関係である、あなたは、同時に値を持つことができませんtag
このような「DIV」、「P」として、実際のDOMのタグ名に対応し、現在のvnodeことを示しますelm
現在の実際のDOM vノードが対応しています
2.パッチ
ヒントのソースコードの複雑な機能を読む:「」「1」を参照。「頭」は(参照を参照して、パラメータを読み込み、抽出することができることを理解することができoldVnode
、vnode
、parentElm
)、「尾部」は、処理機能、戻りの結果を指しますelm
。したがって、「頭尾」の要約によると、patch
新規の終了後vnode
には、対応する生成されelm
、DOMが真であり、それはに装着されたparentElm
時、DOM。単純に、入れVUEインスタンスの初期化など、データの変更等、ページの更新を引き起こし、我々が通過する必要がpatch
ニレを生成する方法。
function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
// ...
const insertedVnodeQueue = []
// ...
if (isUndef(oldVnode)) {
isInitialPatch = true
createElm(vnode, insertedVnodeQueue, parentElm, refElm)
}
// ...
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
}
// ...
return vnode.elm
}
复制代码
パッチプロセス(境界条件の除去)例主に3つの種類があります。
-
何oldVnodeはありませんが、実行されます
createElm
-
そこoldVnodeとvnodeのですが、
sameVnode
falseを返し、行わcreateElm
-
oldVnodeとvノードは、それは、ある
sameVnode
行動は、trueを返しますpatchVnode
3. sameVnode
私は、上記sameVnode
次のように、:
function sameVnode (a, b) {
return (
a.key === b.key && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
)
}
复制代码
単純な場合のために、例えば、前<div>
標識によるロジックの変化への<p>
、ラベル、およびsameVnode
リターンfalse
(a.tag === b.tag
falseを返します)。だから、sameVnode
上記の条件は同じ要素であることを示し、それを行うことができますpatchVnode
。逆に理解があれば、上記の変更のいずれかのように、あなたがすることは必要ありませんということですpathchVnode
から直接、vnode
行動createElm
することができます。
注sameVnode
説明することはできません、trueを返しますが、vノードと同じがここに現在の指標とより一致を意味していることで、子供たちは、変更されている可能性が、まだする必要がpatchVnode
更新されます。
patchVnode
patch
方法、我々が知っているpatchVnode
方法とcreateElm
DOMを対応する電流のvnodeを生成または更新するように、処理方法の最終結果を。
上記の分析の後、結論は必要がDOMを生成する際にということで、vノードの前と後に実行sameVnode
されtrue
、その後行われ、ケースpatchVnode
。
function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
// ...
const elm = vnode.elm = oldVnode.elm
// ...
const oldCh = oldVnode.children
const ch = vnode.children
// ...
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
} else if (isDef(ch)) {
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
// 具体是何种情况下会走到这个逻辑???
removeVnodes(elm, oldCh, 0, oldCh.length - 1)
} else if (isDef(oldVnode.text)) {
nodeOps.setTextContent(elm, '')
}
} else if (oldVnode.text !== vnode.text) {
nodeOps.setTextContent(elm, vnode.text)
}
// ...
}
复制代码
これらは、patchVnode
コードの一部は、ロジックのこの部分を示しているだけでなく、patchVnode
コアの処理ロジック。
多くのの完全な上記のコード、if
else
あなたはいくつかの質問を熟考することができますか?
- vノードのテキスト、vノードchilren存在、不在の子供のvnode:上記のコード分析に基づいて、vnodeのために、それが3 vノードに分けることができます。oldVnode vノードとクロスの組み合わせの場合、それはケースの9種類を持っている必要があり、その後、すべての場合、それをカバーするために上記のコードのすべてがあるのですか?
- これは、例えば、正確にどの
case
に入りますremoveVnodes
ロジック?
実は、これは終わりを読むとき、私は思っ問題がある、私はこの複雑な解決するために、(表を描画するコードに対して)以下の方法を使用しif
else
た論理の解釈を:
oldVnode.text | oldCh | !oldCh | |
---|---|---|---|
vnode.text | setTextContent | setTextContent | setTextContent |
CH | addVnodes | updateChildren | addVnodes |
!CH | setTextContent | removeVnodes | setTextContent |
これは、フォームに対応し、コードに対応して、私はあなたが答えを見つけることができると信じています。
updateChildren
上記の分析の後、のみoldCh
とch
ケースに実装されますが存在しているupdateChildren
場合、パラメータがあるoldCh
とch
、あなたが知ることができるということであるupdateChildren
同じレベルで行われているがchildren
、比較更新、それはdiffの「伝説的」です。
分析を開始する前に、検討することができます。今すぐネイティブDOMを操作するためのJS場合は<ul>
、リストをデータ系列のいずれかが変更された場合、当然のことながら、このリストは実装ネイティブJSによって、今、最初は、エンドまたは特定に排出されるようにされます場所、またはデータを削除、新しいデータを持って、どのように動作します。
let listData = [
'测试数据1',
'测试数据2',
'测试数据3',
'测试数据4',
'测试数据5',
]
let ulElm = document.createElement('ul');
let liStr = '';
for(let i = 0; i < listData.length; i++){
liStr += `<li>${listData[i]}</li>`
}
ulElm.append(liStr)
document.body.innerHTML = ''
document.body.append(ulElm)
复制代码
不確実性の変化に起因する。この時間は、面倒なコードのビジネスロジックを維持するために望んでいないinsertBefore
、、 、、appendChild
我々は最新の情報を取得します、すぐに残忍な解決策であると考えることができ、作成したフローの上面は再度行きます。removeChild
replaceChild
listData
しかしVUEは、単にその意味、差分アルゴリズムを採用しました:
- 以上のように同じことが、まだ最新のを取得する最初のものです
listData
- その後の新しいデータ
_render
操作は新しいのvnodeを取得します - パッチプロセスであり、比較のvnode、前と後
- 同じレベルのノードのために、我々は導通する
updateChildren
操作(差分)、最小限の変更
差分
updateChildren
コードは以下の通りであります:
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx, idxInOld, vnodeToMove, refElm
// removeOnly is a special flag used only by <transition-group>
// to ensure removed elements stay in correct relative positions
// during leaving transitions
const canMove = !removeOnly
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
} else {
vnodeToMove = oldCh[idxInOld]
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !vnodeToMove) {
warn(
'It seems there are duplicate keys that is causing an update error. ' +
'Make sure each v-for item has a unique key.'
)
}
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}
复制代码
分析前、oldCh
及びch
vノードリストの同じレベルを示し、それは、二つの配列であります
変数のセットを定義する作業を始める前に、以下のとおりです。
oldStartIdx
ヘッド部分は、治療すべきoldChポインタを起動し、すなわち、対応するvノードoldStartVnode
oldEndIdx
テール部は、処理すべきoldCh終了ポインタ、すなわち、対応するvノードoldEndVnode
newStartIdx
治療すべき頭部CHポインタを起動し、すなわち、対応するvノードnewStartVnode
newEndIdx
治療されるテール部における端ポインタCH、すなわち、対応するvノードnewEndVnode
oldKeyToIdx
キーは、多くの場合、ループのために書かれているマップ、されるv-bind:key
値、現在のvnodeに対応する値は、つまり、あなたがユニークキーでマップに対応するvノードを見つけることができます
updateChildren
終了条件は、DOM、更新するwhileループを使用した!(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx)
:理解する方法の他の種類では、oldStartIdx > oldEndIdx || newStartIdx > newEndIdx
ループを終了する(次の例では、交差する)それが何を意味し、限り起こっ「断面」が存在するようにします。
栗のために
順序はF、D、A、H、E、C、B、GをCHに更新された後OldCh元の配列は、A、B、C、D、E、F、Gであります
説明
その後のラウンドをより良く理解するために、関連するアコードの開始前に、タグの下に見えます
差分法
ラウンドワン:比較配列:AF - > GG、マッチングが成功し、その後:
- Gに
patchVnode
操作、更新oldEndVnode
G及びnewEndVnode
GのELMを - ポインタの移動、左への2つのテール・ポインタが移動、すなわち、
oldEndIdx--
newEndIdx--
round2:比較配列:AF - > FB - > AB - > FF、 マッチングはその後、成功しています。
- Fはのためにある
patchVnode
更新、操作oldEndVnode
FとnewEndVnode
ニレFさん - ポインタの移動、カーソルの移動、すなわち、
oldEndIdx--
newStartIdx++
- 見出さ
oldStartVnode
位置AにおけるニレDOMは位置、及び、更新Fの前部に挿入され
Round3:比較順序:AD - > EB - > AB - > ED、 失敗し、Dキーを取って、oldKeyToIdx
検索、対応するDを見つけるために、検索は、その後、成功しています。
- Dは、抽出に割り当て
vnodeToMove
- Dのための
patchVnode
操作、更新vnodeToMove
D及びnewStartVnode
DのELMを - ポインタの移動、カーソルの移動、すなわち、
newStartIdx++
- Dに対向oldCh対応したvnode
undefined
- DOMに見出さ
oldStartVnode
ノードAに対応するニレニレ、及び、更新Dの前部に挿入
round4:シーケンスの比較:AA、比較はその後、成功しています。
- うちキャリー
patchVnode
操作は、更新oldStartVnode
とnewStartVnode
のELM Aを - ポインタの移動、左への2つのテール・ポインタが移動、すなわち、
oldStartIdx++
newStartIdx++
round5:比較順序:BH - > EB - > BB 、 比較はその後、成功しています。
- Bは、上にある
patchVnode
操作、更新oldStartVnode
BとnewStartVnode
のELM B - ポインタの移動、すなわち、
oldStartIdx++
newEndIdx--
- DOMは、中に見出さ
oldEndVnode
のE ELMnextSibling
ノード(すなわち、Gのニレ)、その後、更新されたBのニレの前部に挿入します
round6:比較配列:CH - > EC - > CC 、 (round5付き)を成功比較。
- Cは、実行する
patchVnode
操作、更新oldStartVnode
CとnewStartVnode
のELM Cを - ポインタの移動、すなわち、
oldStartIdx++
newEndIdx--
- DOMは、中に見出さ
oldEndVnode
のE ELMnextSibling
ノード(すなわち、ちょうど挿入ニレB)、及び、更新Cニレの前部に挿入します
round7: oldStartVnodeが失敗したゲット(なぜならround3ステップ4)、その後:
- ポインタの移動、すなわち、
oldStartIdx++
round8:比較配列:EH、EE、マッチングが成功し、その後、(ラウンドワン付き):
- Eはために行われ
patchVnode
、動作アップデートoldEndVnode
EとnewEndVnode
ニレのE - ポインタの移動、左への2つのテール・ポインタが移動、すなわち、
oldEndIdx--
newEndIdx--
最後 round8 oldCh後には、ループを終了するために早期に「クロスオーバー」を発生しました。
最終:- 見つかった
newEndIdx+1
要素Aに対応 - 処理すべき部分(すなわち
newStartIdx
-newEndIdx
でのvnode)を直接、パッチなしの部分を添加しましたcreateElm
- 処理すべき全てのこれらの部分は、DOM 1に挿入された位置ELM工程Aの背後に配置されています
いくつかの注意が必要になります。
- oldCh CHおよびその位置は、プロセスでは変更されません。
- 実際の操作がされたに
updateChildren
渡されparentElm
、ニレvノードの父 - ループの各しばらく、私は円形である、コールバック
- 何回か言及し
patchVnode
、楽しみにしてpatchVnode
処理した結果である部分は、oldVnode.elmとvnode.elmを更新されています - 何度もネイティブのオペレーティングDOMがありますが、
insertBefore
フォーカスは最初の挿入する場所を見つけることです
概要
(下優先へ戻る)を以下のように(上記の例に関する)各ラウンド行います。
- いいえ
oldStartVnode
モバイルん(round6を参照してください) - 比較ヘッド、及び正常に移動更新される(round4参照)
- 比較尾、そして正常に更新されるモバイル(ラウンドワンを参照してください)
- 比較頭と尾、そしてモバイルが正常に更新される(round5を参照してください)
- 比較尾、そしてモバイルが正常に更新される(round2を参照してください)
- で
oldKeyToIdx
に係るnewStartVnode
ルックアップを行うことができ、移動体が正常に更新される(Round3参照)(更新と移動:patchVnode対応するvノードELMを更新し、ポインタを移動させます)
いくつかのDOM操作はすぐに行う理由についての質問を挿入し、一部ではないのですか?するとoldStartVnode
ニレ前進するときに、実行されるoldEndVnode
のニレnextSibling
前方のラン?
ただ、ここで覚えている、oldCh
とch
言及している、これはch
我々の目標配列であり、そしてoldCh
我々は、vノードの導入の冒頭で言及されている現在の基準DOMの順序を、理解するために使用。差分ので全体のプロセスは、比較であるoldCh
とch
、現在のラウンドを確認するために、oldCh
どのように近くに移動するch
ので、oldCh
一部がDOM内のまだ処理すべきことができ、oldCh
中にoldStartVnode
ニレ、及びoldEndVnode
どの成功整合素子を決定するために、ELM位置挿入されました。
- 成功の「頭」試合は、現在証明
oldStartVnode
され、位置を移動することなく、現在の位置であるpatchVnode
に更新します - 「テールテールは」「頭」で一致して一致しており、移動せず
- 、すなわち、「尾がうまくマッチした」場合のマッチングが成功し、成功はここで注目され、それはDOMヘッド前方の実行を治療します。ある部分round2、現在係属中の、などすなわちヘッド黒ブロックの一部、。つまり、中ニレの前に挿入ニレを。
oldEndVnode
newSatrtVnode
newSatrtVnode
oldCh
oldStartVnode
oldStartVnode
newSatrtVnode
- すなわち「尾成功したマッチの頭」は、場合同様、マッチングが成功し、成功は、ことに留意され(、前方の次の要素は、要素の末尾に実行されることである)尾部は、それがDOM挿入を処理すべきです。ある部分round5、現在係属中の、などすなわち尾黒ブロックの一部、。つまり、最初に見つけにおけるニレの挿入の前ニレのを。
oldStartVnode
newEndVnode
newEndVnode
oldCh
oldEndVnode
oldEndVnode
nextSibling
newEndVnode
(ここで注意は、あなたが具体的概略図を見ることができ、「処理対象のブロック」と呼ばれるoldCh
処理対象のブロック部とDOM部分で処理されます)
すでに含まれている上記のupdateChildren
内容のほとんどは、もちろん、私たちが行くことができます全体のプロセスの例を見つけるために、ソースコードを見ることができ、特定のそれらを設定していないカバーされていないこと、いくつかを、そこにあります。
最後に、答えなかった問題があり、insertedVnodeQueue
使用は何ですか?なぜ、とされて?
このセクションでは、単に下にすることができるパッチ・コンポーネントのプロセスを指す:アセンブリ$mount
機能は、コンポーネントインスタンストリガしない直後後にmounted
フックを、しかし現在のインスタンスpush
へinsertedVnodeQueue
のパッチの最後の行に、第2の、そして、それは実行しますinvokeInsertHook
、すべてのコンポーネントインスタンスのためのトリガであるinsert
フック組立insert
フック関数は、コンポーネントインスタンストリガするmounted
フック。例えば、パッチの過程で、パッチの複数のコンポーネントのvnode、彼らが行った$mount
DOMを生成するが、すぐにトリガされませんでした$mounted
が、そう全体のpatch
完全な、1つのトリガずつ。
ます。https://juejin.im/post/5cfbee14e51d45776147610dで再現