Patch source code analysis of vue virtual dom, vuedompatch source code
This article introduces the patch source code analysis of vue virtual dom and shares it with you, as follows:
Source code directory:src/core/vdom/patch.js
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 const canMove =! RemoveOnly while (oldStartIdx <= oldEndIdx & newStartIdx <= newEndIdx) {// if (isUndef (oldStartVnode )) {oldStartVnode = oldCh [++ oldStartIdx] // The Vnode has been removed.} Else if (isUndef (oldEndVnode) {oldEndVnode = oldCh [-- oldEndIdx]} else if (sameVnode (oldStartVnode, newStartVnode) {patchVnode (worker, newStartVnode, worker) oldStartVnode = oldCh [++ oldStartIdx] // Add 1 to the index. Is to compare the next node. For example, if start = a [0], start = a [1], after changing the start value, compare the vnode newStartVnode = newCh [++ newStartIdx]} else if (sameVnode (oldEndVnode, newEndVnode) {patchVnode (oldEndVnode, newEndVnode, insertedVnodeQueue) oldEndVnode = oldCh [-- oldEndIdx] newEndVnode = newCh [-- newEndIdx]} else if (sameVnode (callback, newEndVnode) {patchVnode (callback, newEndVnode, callback) canMove & nodeOps. inser TBefore (parentElm, oldStartVnode. elm, nodeOps. nextSibling (oldEndVnode. (elm) // move Node B to the rightmost oldStartVnode of the tree = oldCh [++ oldStartIdx] newEndVnode = newCh [-- newEndIdx]} else if (sameVnode (oldEndVnode, newStartVnode )) {old. end. d = new. start. d patchVnode (oldEndVnode, newStartVnode, insertedVnodeQueue) canMove & nodeOps. insertBefore (parentElm, oldEndVnode. elm, oldStartVnode. elm) // Vnode moved left, move d to the left of c. = Old. start-> old. end oldEndVnode = oldCh [-- oldEndIdx] newStartVnode = newCh [++ newStartIdx]} else {if (isUndef (latency) Then = createKeyToOldIdx (oldCh, oldStartIdx, oldEndIdx) idxInOld = isDef (newStartVnode. key )? OldKeyToIdx [newStartVnode. key]: findIdxInOld (newStartVnode, oldCh, oldStartIdx, oldEndIdx) if (isUndef (idxInOld) {createElm (newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode. elm) // create a new node, followed by nodeOps. insertBefore (parent, elm, ref)} 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. tre At 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, callback)} else if (newStartIdx> newEndIdx) {removeVnodes (parentElm, oldCh, oldStartIdx, oldEndIdx) // Delete the old c, removeNode (ch. elm )}}
Function sameVnode (a, B) {return (. key = B. key & (. tag = B. tag &. isComment = B. isComment & isDef (. data) === isDef (B. data) & sameInputType (a, B) | (isTrue (. isAsyncPlaceholder) &. asyncFactory = B. asyncFactory & isUndef (B. asyncFactory. error)}/*** compare the old and new vnode nodes, and perform reasonable dom update operations (ADD, move, delete) based on different states) the entire process also calls the prepatch, update, postpatch and other Hook Functions in sequence, and some static subtree generated during the compilation phase. In this process * @ param oldVno In de, the comparison is skipped directly because it does not change. In the comparison process, the core part of the dynamic subtree is that when the new and old vnodes both have children, the child node is updated using the updateChildren method, * @ param vnode * @ param insertedVnodeQueue * @ param removeOnly */function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {if (oldVnode = vnode) {return} const elm = vnode. elm = oldVnode. elm if (isTrue (oldVnode. isAsyncPlaceholder) {if (isDef (vnode. asyncFactory. resolved) {hydrate (oldVnode. elm, Vnode, insertedVnodeQueue)} else {vnode. isAsyncPlaceholder = true} return} // reuse element for the static tree. // Note that if the vnode is cloned, we only do this. // If the new node is not cloned, it indicates the rendering function. // Re-set by the hot reload api. We need to perform appropriate re-rendering. If (isTrue (vnode. isStatic) & isTrue (oldVnode. isStatic) & vnode. key = oldVnode. key & (isTrue (vnode. isCloned) | isTrue (vnode. isOnce) {vnode. componentInstance = oldVnode. componentInstance return} let I const data = vnode. data if (isDef (data) & isDef (I = data. hook) & isDef (I = I. prepatch) {I (oldVnode, vnode)} const oldCh = oldVnode. children const ch = vnode. children if (isDef (data )&& IsPatchable (vnode) {for (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) {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)} if (isDef (data) {if (isDef (I = data. hook) & isDef (I = I. postpatch) I (oldVnode, vnode)} function insertBefore (parentNode, newNode, referenceNode) {parentNode. insertBefore (newNode, referenceNode);}/***** @ param vnode creates a real dom Node Based on the vnode data structure. If vnode has children, it traverses these subnodes, recursively call the createElm method. * @ param insertedVnodeQueue records the sequence of creation of queues for subnodes. Each time a dom element is created Insert the current vnode into the queue. When all the vnode objects are converted into real dom trees, the insert Method * @ param parentElm * @ param refElm * @ param nested */let inPre = 0 function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) {vnode. isRootInsert =! Nested // transition to check if (createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {return} const data = vnode. data const children = vnode. children const tag = vnode. tag if (isDef (tag) {if (process. env. NODE_ENV! = 'Production ') {if (data & data. pre) {inPre ++} if (! InPre &&! Vnode. ns &&! (Config. ignoredElements. length & config. ignoredElements. some (ignore => {return isRegExp (ignore )? Ignore. test (tag): ignore ===tag}) & config. isUnknownElement (tag) {warn ('unknown M element: <'+ tag +'>-did you '+ 'register the component correctly? For recursive components, '+ 'make sure to provide the "name" option.', vnode. context)} vnode. elm = vnode. ns? NodeOps. createElementNS (vnode. ns, tag): nodeOps. createElement (tag, vnode) setScope (vnode)/* istanbul ignore if */if (_ WEEX _) {// in Weex, the default insertion order is parent-first. // List items can be optimized to use children-first insertion // with append = "tree ". const appendAsTree = isDef (data) & isTrue (data. appendAsTree) if (! AppendAsTree) {if (isDef (data) {invokeCreateHooks (vnode, insertedVnodeQueue)} insert (parentElm, vnode. elm, refElm)} createChildren (vnode, children, insertedVnodeQueue) if (appendAsTree) {if (isDef (data) {partition (vnode, insertedVnodeQueue)} insert (parentElm, vnode. elm, refElm)} else {createChildren (vnode, children, insertedVnodeQueue) if (isDef (data) {invokeCreateHooks (vnode, InsertedVnodeQueue)} insert (parentElm, vnode. elm, refElm)} if (process. env. NODE_ENV! = 'Production '& data. pre) {inPre --} else if (isTrue (vnode. isComment) {vnode. elm = nodeOps. createComment (vnode. text) insert (parentElm, vnode. elm, refElm)} else {vnode. elm = nodeOps. createTextNode (vnode. text) insert (parentElm, vnode. elm, refElm)} function insert (parent, elm, ref) {if (isDef (parent) {if (isDef (ref) {if (ref. parentNode = parent) {nodeOps. insertBefore (parent, elm, ref)} else {nodeOps. appendChild (parent, elm) }}function removeVnodes (parentElm, vnodes, startIdx, endIdx) {for (; startIdx <= endIdx; ++ startIdx) {const ch = vnodes [startIdx] if (isDef (ch) {if (isDef (ch. tag) {removeAndInvokeRemoveHook (ch) invokeDestroyHook (ch)} else {// Text node removeNode (ch. elm )}}}}
updateChildrenThe main method is throughwhileCompare the subnodes of the two trees to update them cyclically.domTo change the old one by comparing the new one.
An example is used to simulate:
Assume there are two new and old trees, and the subnodes in the tree area,b,c,dDifferent codes represent differentvnodeSuch:
After the status is set, we start the first comparison.oldStartVnode=a,newStartVnode=a;HitsameVnode(oldStartVnode,newStartVnode)Logic, then directly callpatchVnode(oldStartVnode,newStartVnode,insertedVnodeQueue)Method update NodeaThenoldStartIdxAndnewStartIdxIndex + 1,
Node updatedaThen, we start the comparison for 2nd times.oldStartVnode=b,newEndVnode=b;HitsameVnode(oldStartVnode,newEndVnode)Logic, callpatchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)Method update NodebAnd then callcanMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)), Set the nodebMove to the rightmost of the tree, and finallyoldStartIdxIndex + 1,newEndIdxIndex-1,
Node updatedbThen, we start the third comparison.oldEndVnode=d,newStartVnode=d;HitsameVnode(oldEndVnode, newStartVnode)Logic, callpatchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)Method update NodedAnd then callcanMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)dMovecTo the left. FinallyoldEndIdxIndex-1,newStartIdxIndex + 1,
UpdateddThen, we start the comparison for 4th times.newStartVnode=e, NodeeIt does not exist in the old tree. Therefore, it should be inserted as a new element and calledcreateElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm), FollowednodeOps.insertBefore(parent, elm, ref)MethodeInsertcBeforenewStartIdxIndex + 1,
Insert NodeeWe can seenewStartIdxAlready greaternewEndIdxNow,whileThe loop has been completed. CallremoveVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)Delete oldc, Eventually
updateChildrenAfter performing the preceding steps to update the old tree subnode, you actually only use a relatively smalldomThe operation improves the performance. The more complex the sub-node is, the more obvious the improvement is.vnodePasspatchMethod generationdomWill callmounted hookSo far, the entirevueThe instance is created.vueInstancewatcherWhen the data changes, it is called twice.renderMethod to generate a newvnodeAnd then callpatchMethod ComparisonvnodeTo updatedom.
The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.