-diffを解析するソースコードを見つけるためにVUEプロセス

またのような、ない詳細な説明の内容の一部に、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」を参照。「頭」は(参照を参照して、パラメータを読み込み、抽出することができることを理解することができoldVnodevnodeparentElm)、「尾部」は、処理機能、戻りの結果を指します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のですが、sameVnodefalseを返し、行わ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リターンfalsea.tag === b.tagfalseを返します)。だから、sameVnode上記の条件は同じ要素であることを示し、それを行うことができますpatchVnode逆に理解があれば、上記の変更のいずれかのように、あなたがすることは必要ありませんということですpathchVnodeから直接、vnode行動createElmすることができます。

sameVnode説明することはできません、trueを返しますが、vノードと同じがここに現在の指標とより一致を意味していることで、子供たちは、変更されている可能性が、まだする必要がpatchVnode更新されます。

patchVnode

patch方法、我々が知っているpatchVnode方法とcreateElmDOMを対応する電流の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あなたはいくつかの質問を熟考することができますか?

  1. vノードのテキスト、vノードchilren存在、不在の子供のvnode:上記のコード分析に基づいて、vnodeのために、それが3 vノードに分けることができます。oldVnode vノードとクロスの組み合わせの場合、それはケースの9種類を持っている必要があり、その後、すべての場合、それをカバーするために上記のコードのすべてがあるのですか?
  2. これは、例えば、正確にどのcaseに入りますremoveVnodesロジック?

実は、これは終わりを読むとき、私は思っ問題がある、私はこの複雑な解決するために、(表を描画するコードに対して)以下の方法を使用しif elseた論理の解釈を:

oldVnode.text oldCh !oldCh
vnode.text setTextContent setTextContent setTextContent
CH addVnodes updateChildren addVnodes
!CH setTextContent removeVnodes setTextContent

これは、フォームに対応し、コードに対応して、私はあなたが答えを見つけることができると信じています。

updateChildren

上記の分析の後、のみoldChchケースに実装されますが存在しているupdateChildren場合、パラメータがあるoldChch、あなたが知ることができるということである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 我々は最新の情報を取得します、すぐに残忍な解決策であると考えることができ、作成したフローの上面は再度行きます。removeChildreplaceChildlistData

しかしVUEは、単にその意味、差分アルゴリズムを採用しました:

  1. 以上のように同じことが、まだ最新のを取得する最初のものですlistData
  2. その後の新しいデータ_render操作は新しいのvnodeを取得します
  3. パッチプロセスであり、比較のvnode、前と後
  4. 同じレベルのノードのために、我々は導通する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及びchvノードリストの同じレベルを示し、それは、二つの配列であります

変数のセットを定義する作業を始める前に、以下のとおりです。

  • 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、マッチングが成功し、その後:

  1. GにpatchVnode操作、更新oldEndVnodeG及びnewEndVnodeGのELMを
  2. ポインタの移動、左への2つのテール・ポインタが移動、すなわち、oldEndIdx-- newEndIdx--

round2:比較配列:AF - > FB - > AB - > FF、 マッチングはその後、成功しています。

  1. FはのためにあるpatchVnode更新、操作oldEndVnodeFとnewEndVnodeニレFさん
  2. ポインタの移動、カーソルの移動、すなわち、oldEndIdx-- newStartIdx++
  3. 見出さoldStartVnode位置AにおけるニレDOMは位置、及び、更新Fの前部に挿入され

Round3:比較順序:AD - > EB - > AB - > ED、 失敗し、Dキーを取って、oldKeyToIdx検索、対応するDを見つけるために、検索は、その後、成功しています。

  1. Dは、抽出に割り当て vnodeToMove
  2. DのためのpatchVnode操作、更新vnodeToMoveD及びnewStartVnodeDのELMを
  3. ポインタの移動、カーソルの移動、すなわち、newStartIdx++
  4. Dに対向oldCh対応したvnodeundefined
  5. DOMに見出さoldStartVnodeノードAに対応するニレニレ、及び、更新Dの前部に挿入

round4:シーケンスの比較:AA、比較はその後、成功しています。

  1. うちキャリーpatchVnode操作は、更新oldStartVnodenewStartVnodeのELM Aを
  2. ポインタの移動、左への2つのテール・ポインタが移動、すなわち、oldStartIdx++ newStartIdx++

round5:比較順序:BH - > EB - > BB 、 比較はその後、成功しています。

  1. Bは、上にあるpatchVnode操作、更新oldStartVnodeBとnewStartVnodeのELM B
  2. ポインタの移動、すなわち、oldStartIdx++ newEndIdx--
  3. DOMは、中に見出さoldEndVnodeのE ELM nextSiblingノード(すなわち、Gのニレ)、その後、更新されたBのニレの前部に挿入します

round6:比較配列:CH - > EC - > CC 、 (round5付き)を成功比較。

  1. Cは、実行するpatchVnode操作、更新oldStartVnodeCとnewStartVnodeのELM Cを
  2. ポインタの移動、すなわち、oldStartIdx++ newEndIdx--
  3. DOMは、中に見出さoldEndVnodeのE ELM nextSiblingノード(すなわち、ちょうど挿入ニレB)、及び、更新Cニレの前部に挿入します

round7: oldStartVnodeが失敗したゲット(なぜならround3ステップ4)、その後:

  1. ポインタの移動、すなわち、oldStartIdx++

round8:比較配列:EH、EE、マッチングが成功し、その後、(ラウンドワン付き):

  1. Eはために行われpatchVnode、動作アップデートoldEndVnodeEとnewEndVnodeニレのE
  2. ポインタの移動、左への2つのテール・ポインタが移動、すなわち、oldEndIdx-- newEndIdx--

最後 round8 oldCh後には、ループを終了するために早期に「クロスオーバー」を発生しました。

最終:

  1. 見つかったnewEndIdx+1要素Aに対応
  2. 処理すべき部分(すなわちnewStartIdx- newEndIdxでのvnode)を直接、パッチなしの部分を添加しましたcreateElm
  3. 処理すべき全てのこれらの部分は、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前方のラン?

ただ、ここで覚えている、oldChch言及している、これはch我々の目標配列であり、そしてoldCh我々は、vノードの導入の冒頭で言及されている現在の基準DOMの順序を、理解するために使用。差分ので全体のプロセスは、比較であるoldChch、現在のラウンドを確認するために、oldChどのように近くに移動するchので、oldCh一部がDOM内のまだ処理すべきことができ、oldCh中にoldStartVnodeニレ、及びoldEndVnodeどの成功整合素子を決定するために、ELM位置挿入されました。

  • 成功の「頭」試合は、現在証明oldStartVnodeされ、位置を移動することなく、現在の位置であるpatchVnodeに更新します
  • 「テールテールは」「頭」で一致して一致しており、移動せず
  • 、すなわち、「尾がうまくマッチした」場合のマッチングが成功し、成功はここで注目され、それはDOMヘッド前方の実行を治療します。ある部分round2、現在係属中の、などすなわちヘッド黒ブロックの一部、つまり、中ニレの前に挿入ニレを。oldEndVnodenewSatrtVnodenewSatrtVnodeoldCholdStartVnodeoldStartVnodenewSatrtVnode
  • すなわち「尾成功したマッチの頭」は、場合同様、マッチングが成功し、成功は、ことに留意され(、前方の次の要素は、要素の末尾に実行されることである)尾部は、それがDOM挿入を処理すべきです。ある部分round5、現在係属中の、などすなわち尾黒ブロックの一部、つまり、最初に見つけにおけるニレの挿入の前ニレのを。oldStartVnodenewEndVnodenewEndVnodeoldCholdEndVnodeoldEndVnodenextSiblingnewEndVnode

(ここで注意は、あなたが具体的概略図を見ることができ、「処理対象のブロック」と呼ばれるoldCh処理対象のブロック部とDOM部分で処理されます)

すでに含まれている上記のupdateChildren内容のほとんどは、もちろん、私たちが行くことができます全体のプロセスの例を見つけるために、ソースコードを見ることができ、特定のそれらを設定していないカバーされていないこと、いくつかを、そこにあります。


最後に、答えなかった問題があり、insertedVnodeQueue使用は何ですか?なぜ、とされて?

このセクションでは、単に下にすることができるパッチ・コンポーネントのプロセスを指す:アセンブリ$mount機能は、コンポーネントインスタンストリガしない直後後にmountedフックを、しかし現在のインスタンスpushinsertedVnodeQueueのパッチの最後の行に、第2の、そして、それは実行しますinvokeInsertHook、すべてのコンポーネントインスタンスのためのトリガであるinsertフック組立insertフック関数は、コンポーネントインスタンストリガするmountedフック。例えば、パッチの過程で、パッチの複数のコンポーネントのvnode、彼らが行った$mountDOMを生成するが、すぐにトリガされませんでした$mountedが、そう全体のpatch完全な、1つのトリガずつ。

ます。https://juejin.im/post/5cfbee14e51d45776147610dで再現

おすすめ

転載: blog.csdn.net/weixin_34237596/article/details/91451080