vue process to find out the source code parsing -diff

Also read other articles speak vue diff process, but it feels just talked about some of them (compare mode), not on the part of the details of the detailed explanations, such as

  • If a match is made patchVnodeis doing what? Why is there to be followed dom operation, some do not?
  • During the diff, the pointer of how specific mobile? And which parts have changed?
  • insertedVnodeQueueWhat is the use? Why been with?
  • Then also puzzled for a long time, a lot of articles in this part of the direct oldChildren mobile operations, but oldChildren move happen? So in the end who happened to move it?

Here it does not directly began to speak diff, in order so that we can know the detailed process of diff, before beginning the core of where some simple concepts and processes need to explain in advance what course is best for the hope that you have this patch vue Source part of some understanding.

Several concepts

Since the core is a process diff, it will first diff related to the core concepts briefly explain, if these still have questions can leave a message in the comments section:

1. vnode

Simply means that real dom description of the object, which is also one of the characteristics of vue - virtual dom. Since dom structure of native too complicated, when it is desired to obtain and understand the node information of the time, does not require complex operations dom, corresponding vue is first analyzed (diff Comparative i.e. contrast the vnode) with which describes the object, and then the reaction to the real 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 () {
    ...
  }

}
复制代码

Note that later will be related to several properties:

  • childrenAnd parentestablish hierarchical relationships between its vnode through this, which is the corresponding hierarchy of real dom
  • text If there is value to prove that the corresponding vnode is a document node with children is a mutually exclusive relationship, you can not simultaneously have value
  • tag Indicates that the current vnode, corresponding to the real dom tag name, such as 'div', 'p'
  • elm Is the current real dom vnode corresponding

2. patch

Reading the source code complex functions of tips: see 'a' 'one'. 'Head' refers to the reference, be able to read and extract the parameters can understand ( oldVnode, vnode, parentElm), 'tail' refers to a result of the processing function, the return elm. Therefore, according to the 'head to tail' summary, patchafter completion of the new vnodeon will generate a corresponding elm, dom is true, and that has been mounted to the parentElmdom at. Simply put, as vue instance initialization, data change caused the page updates, etc., we have to go through patchmethod to generate elm.

  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
  }
复制代码

patch process (removal of boundary conditions) have mainly three kinds of case:

  • There is no oldVnode, is carried outcreateElm

  • There is oldVnode and vnode, but sameVnodereturns false, carried outcreateElm

  • There is oldVnode and vnode, it sameVnodereturns true, the conductpatchVnode

3. sameVnode

I mentioned above sameVnode, as follows:

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)
      )
    )
  )
}
复制代码

For the simple case, for example, before a <div>label, due to changes in logic, into a <p>label, and then sameVnodereturns false( a.tag === b.tagreturns false). So sameVnodeshow that the above conditions is the same element, it can be conducted patchVnode. Conversely understanding is that as long as any of the above changes, you do not need be pathchVnode, directly from the vnodeconduct createElmcan be.

Note that sameVnodereturns true, can not explain is that the same as a vnode here means more consistent with the current indicators, their children may have changed, still need to patchVnodebe updated.

patchVnode

By the patchway, we know the patchVnodemethod and the createElmfinal result of processing methods, as is to generate or update the current vnode corresponding dom.

After the above analysis, the conclusion is that when the need to generate dom, and performed before and after the vnode sameVnodeis truethe case, then performed 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)
    }
    // ...
  }
复制代码

These are patchVnodepart of the code, it shows this part of the logic, but also patchVnodethe core processing logic.

The above code, full of a lot of if else, you can ponder a few questions?

  1. Based on the above analysis of the code, for a vnode, it can be divided into three vnode: vnode text, the vnode chilren presence, absence Children's vnode. For oldVnode vnode and cross combination, it should have nine kinds of case, then there are all of the above code to cover all case it?
  2. That, for example, exactly which casewill enter into removeVnodesthe logic?

Actually, this is the problem I thought when reading the end, I used the following way (against the code to draw a table) to solve this complex if elseinterpretation of logic:

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

It corresponds to the form, and corresponds to the code, I believe you can find the answer.

updateChildren

After the above analysis, only oldChand chwill be implemented in the case are present updateChildren, then the parameters are oldChand ch, so you can know is that updateChildrencarried out at the same level is childrenupdated comparison, that is 'legendary' diff the .

Before starting analysis, the can consider: If now js to operate a native dom the <ul>list, of course, this list is by native js implemented, now if one of the data sequence is changed, the first to be discharged to the end or the specific a location, or have new data, deleting data, how to operate.

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)
复制代码

This time due to changes in uncertainty, does not want to maintain the business logic in the code tedious insertBefore, , appendChild, removeChild, replaceChildcan think immediately brutal solution is, we get the latest listData, the top surface of the flow created go again.

However vue adopted a diff algorithm, simply means that:

  1. Or the same as above, is still the first to get the latestlistData
  2. Then new data for _renderthe operation to obtain new vnode
  3. Before and after comparison vnode, which is patch process
  4. For nodes at the same level, we will conduct updateChildrenoperations (diff), minimal changes

diff

updateChildrencode show as below:

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)
    }
  }
复制代码

Prior to the analysis, oldChand chrepresents the same level of vnode list, that is, two arrays

Before you begin defines a set of variables are as follows:

  • oldStartIdx Start pointer oldCh the head portion to be treated, i.e. the corresponding vnodeoldStartVnode
  • oldEndIdx End pointer oldCh the tail portion to be treated, i.e. the corresponding vnodeoldEndVnode
  • newStartIdx Start pointer ch the head portion to be treated, i.e. the corresponding vnodenewStartVnode
  • newEndIdx End pointer ch in the tail portion to be treated, i.e. the corresponding vnodenewEndVnode
  • oldKeyToIdxIs a map, where the key is often written in the for loop v-bind:keyvalue, value that corresponds to the current vnode, that is, you can find the corresponding vnode in the map by a unique key

updateChildrenUsing a while loop to update dom, where the exit condition is !(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx), in other kinds of ways to understand: oldStartIdx > oldEndIdx || newStartIdx > newEndIdxwhat does it mean, as long as there is a happening 'cross' (The following example will cross) to exit the loop.

For chestnuts

OldCh original sequence is A, B, C, D, E, F, G, after the order is updated to ch F, D, A, H, E, C, B, G.

Illustrate

For a better understanding of the subsequent round, look under the tag before the start of the relevant accord

diff process

Round1: Comparison sequence: AF -> GG, the matching is successful, then:

  1. To G in patchVnodethe operation, update oldEndVnodeG and newEndVnodeELM of G
  2. Pointer movement, two tail pointer moves to the left, i.e.,oldEndIdx-- newEndIdx--

round2: Comparison sequence: AF -> FB -> AB -> FF, the matching is successful, then:

  1. F is for patchVnodeoperation, updating oldEndVnodeF and newEndVnodeelm F's
  2. Pointer movement, cursor movement, i.e.,oldEndIdx-- newStartIdx++
  3. Found oldStartVnodeelm dom in position A is located, and then inserted at the front of the updated F

Round3: Comparison sequence: AD -> EB -> AB -> ED, unsuccessful, taking the D key, the oldKeyToIdxlookup, to find the corresponding D, the search is successful, then:

  1. D assigned to the extracted vnodeToMove
  2. For the D patchVnodeoperation, update vnodeToMoveD and newStartVnodeELM of D
  3. Pointer movement, cursor movement, i.e.,newStartIdx++
  4. The oldCh corresponding vnode opposed to Dundefined
  5. Found in the dom oldStartVnodeelm elm corresponding to the node A, and then inserted at the front of the updated D

round4: comparative sequence: AA, comparison is successful, then:

  1. A carry out of patchVnodeoperation, updates oldStartVnodeA and newStartVnodeELM A of
  2. Pointer movement, two tail pointer moves to the left, i.e.,oldStartIdx++ newStartIdx++

round5: Comparison sequence: BH -> EB -> BB , comparison is successful, then:

  1. B is on the patchVnodeoperation, update oldStartVnodeB and newStartVnodeELM B of
  2. Pointer movement, i.e.,oldStartIdx++ newEndIdx--
  3. Dom found in oldEndVnodeE elm of the nextSiblingnode (i.e., the G elm), and then inserted at the front of the updated B elm

round6: Comparison sequence: CH -> EC -> CC , a successful comparison, then (with round5):

  1. C to carry out patchVnodethe operation, update oldStartVnodeC and newStartVnodeELM C of
  2. Pointer movement, i.e.,oldStartIdx++ newEndIdx--
  3. Dom found in oldEndVnodeE elm of the nextSiblingnode (i.e., the just inserted elm B), and then inserted at the front of the updated C elm

round7: Get oldStartVnode fails (because round3 step 4), then:

  1. Pointer movement, i.e.,oldStartIdx++

round8: Comparison sequence: EH, EE, the matching is successful, then (with round1):

  1. E conducted to patchVnodeoperate, update oldEndVnodeE and newEndVnodeE of elm
  2. Pointer movement, two tail pointer moves to the left, i.e.,oldEndIdx-- newEndIdx--

last after round8 oldCh occurred early 'crossover' to exit the loop.

last:

  1. Found newEndIdx+1corresponding to element A
  2. Portion to be processed (i.e. newStartIdx- newEndIdxthe vnode in) was added portion without patch, directcreateElm
  3. All these parts to be treated, are located behind the inserted position elm Step A in dom 1

Need some attention:

  • oldCh ch and their position does not change in the process
  • Into the real operation is updateChildrenpassed parentElm, father of elm vnode
  • while each of the loop, and I call back, which is round
  • Mentioned several times patchVnode, look forward patchVnodeportion, which is the result of the processing has been updated oldVnode.elm and vnode.elm
  • There are many times the native operating dom, the insertBeforefocus is to first find a place to insert

to sum up

Each round (related to the above example) do as follows (top to bottom priority):

  • No oldStartVnodemobile (see round6)
  • Comparative head, and is updated successfully moved (refer to round4)
  • Comparative tail, and the mobile is updated successfully (see Round1)
  • Comparative head and tail, and the mobile is updated successfully (see round5)
  • Comparative tails, and the mobile is updated successfully (see round2)
  • In oldKeyToIdxaccording to newStartVnodethe lookup can be performed, and the mobile is updated successfully (see Round3) (updated and movement: patchVnode updates the corresponding vnode the ELM, and move the pointer)

Insert questions about why some dom operation immediately carried out, some do not? When in oldStartVnodethe elm forward runs, when at oldEndVnodethe elm of nextSiblingforward runs?

Just remember here, oldChand chare reference, which chis our goal sequence, and oldChwe used to understand the current reference dom order, which is mentioned at the beginning of the introduction of vnode. Diff so the whole process, is the comparison oldChand ch, to confirm the current round, oldChhow to move closer ch, since the oldChpart to be treated is still in the dom, it can oldChin oldStartVnodethe elm, and oldEndVnodeposition elm to determine how successful matching elements insert.

  • 'Head' match on success proves the current oldStartVnodeposition is the current position without moving, be patchVnodeupdated to
  • 'Tail tail' matches with the 'head' matches, and without moving
  • If 'tails successfully matched', i.e., oldEndVnodethe newSatrtVnodematching is successful, success is noted here newSatrtVnode, it is to be treated dom head forward runs. As part round2, currently pending, which is oldChpart of the black blocks, i.e. the head oldStartVnode. That is, in oldStartVnodefront of the elm insert newSatrtVnodethe elm.
  • Similarly, if the 'head to tail successful match', i.e., oldStartVnodethe newEndVnodematching is successful, success is noted here that newEndVnodethe tail, it is to be treated dom insertion (that is, the next element of the forward runs trailing elements). As part round5, currently pending, which is oldChpart of the black blocks, i.e. the tail oldEndVnode. That is, first find oldEndVnodethe elm in nextSiblingfront of the insertion newEndVnodeof the elm.

(Here referred to 'block to be processed', you can see a schematic diagram specifically, attention oldChto be processed in the block portion and dom portion to be processed)

Above already contains updateChildrenmost of the content, of course, there are some that are not covered not setting them, specific we can look at the source code, to find examples of the whole process can go.


Finally, there is a problem did not answer, insertedVnodeQueuewhat is the use? Why been with?

This section refers to the process of patch components, where you can simply under: assembly $mountafter immediately after the function does not trigger component instance mountedhook, but the current instance pushto insertedVnodeQueue, and then the second to last line in the patch, it will perform invokeInsertHook, which is the trigger for all component instances of inserthooks and assembly inserthook function will trigger component instance mountedhook. For example, in the course of the patch, patch multiple components vnode, they have carried out $mountwhich generates dom, but did not trigger immediately $mounted, but so the whole patchcomplete, one by one trigger.

Reproduced in: https: //juejin.im/post/5cfbee14e51d45776147610d

Guess you like

Origin blog.csdn.net/weixin_34237596/article/details/91451080