preface
This paper aims at the main logic and key details of diff algorithm in vue.
Starting from a simple demo: p tag renders an array of items
<div id="demo">
<p v-for="item in items" :key="item">{{ item }}</p>
</div>
<script src="../vue-source/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#demo",
data: {
items: ["a", "b", "c", "d", "e"]
},
mounted() {
setTimeout(() => {
this.items.splice(2, 0, "f")
}, 2000)
}
})
</script>
Copy code
First, explain the actual sequence:
- Item data changes Dep.notify
- patch(oldVNode, vnode, ...)
- Patch vnode (oldvnode, vnode, insertedVnodeQueue,...) PS: diff started from here. insertedVnodeQueue is a constant defined in the patch function, which has been maintained in the diff in the later period. It is a typical closure structure.
- The core method of updateChildren() diff
5. If you want to learn to practice together with the actual combat, you can get the latest enterprise level Vue3.0/Js/ES6/TS/React/node and other real combat video tutorials in 2020. You can get them in 519293536 for free, and you can't get them in Xiaobai!
sameVnode
sameVnode function runs through the whole diff process, and the first necessary condition is that the key must be equal
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)
)
)
)
}
Copy code
key
As we all know, key plays an important role in patch. Key can effectively reduce unnecessary re rendering in many cases. When the key is not set, the sub element key in the rendering list data is undefined, obviously undefined === undefined. sameVNode is always the same (usually), and it will cause unnecessary rendering (if the key is not set in the first demo, it will cause unnecessary rendering 3 times more).
If the key is set, a.key! = = b.key, the judgment will be terminated immediately. sameVnode directly returns false instead of bb.
Avoid using array subscripts as key s
Because when the array changes, the subscript may also change, which may lead to some hidden bug s.
patch
- If there is no oldVnode, create elm
- oldVnode and vnode exist, but sameVnode returns false, then createElm
- oldVnode and vnode exist, but sameVnode returns true, patchVnode
patchVnode
Vnode can be divided into three types:
- Plain text Vnode
- Vnode with Children
- Vnode without Children
So it can be divided into 3 * 3 situations
oldVnode.text | oldCh | !oldCh | |
---|---|---|---|
vnode.text | setTextContent | setTextContent | setTextContent |
ch | addVnodes | updateChildren | addVnodes |
!ch | setTextContent | removeVnodes | setTextContent |
function patchVnode (oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
// If the nodes are the same, they will be returned directly without processing
if (oldVnode === vnode) {
return
}
// ...
const elm = vnode.elm = oldVnode.elm
// ...
const oldCh = oldVnode.children
const ch = vnode.children
// ...
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
// When new and old Vnode.length All exist and are not equal to enter updateChildren
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)
}
// ...
}
Copy code
updateChildren
insertedVnodeQueue is an array queue maintained. After diff is completed, the data in the queue will be updated one by one
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
// Double pointer
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
// First = > last = > first = > last = > traverse old and find index instead
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, newCh, newStartIdx)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
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, newCh, newStartIdx)
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, false, newCh, newStartIdx)
} else {
vnodeToMove = oldCh[idxInOld]
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
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, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
// Jumping out of the while loop means that the front and back pointers are interlaced
// If the pointer of the old node is interleaved first, it means that a new node is added = > addvnodes
// Otherwise = > removevnodes
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(oldCh, oldStartIdx, oldEndIdx)
}
Copy code
test
Back to the example, in order to get more information, change the demo.
<body>
<div id="demo">
<p v-for="item in items" :key="item">{{ item }}</p>
</div>
<div>
["a", "b", "c", "d", "e", "f", "g"] => ["f", "d", "a", "h", "e", "c", "b", "g"]
</div>
<script src="../vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#demo",
data: {
items: ["a", "b", "c", "d", "e", "f", "g"]
},
mounted() {
setTimeout(() => {
this.items = ["f", "d", "a", "h", "e", "c", "b", "g"]
}, 2000)
}
})
</script>
</body>
Copy code
stay vue.js updateChildren in for observation.
First while
Tail gg match successful
newStartVnode is g, enter patchVnode. I thought I would judge if( oldVnode.text !== vnode.text )Then I didn't deal with it, but I went back to updateChildren.
Can't help but let me console.log("oldStartIdx", oldStartIdx, oldCh[oldStartIdx])
misunderstanding
Where the structure is
I thought that VNode.text = a; VNode.children = undefined ...
A in this < p > a < / P > is also a VNode
Fortunately, the error has been corrected, and the pure text VNode will be ignored.
I'm not the only one who knows now
Then add a condition in debugger to observe oldStartVnode.tag === 'p'
continue
Second while
The tail head matching is successful. The reference node is a node nodeOps.insertBefore
abcdefg => fabcdeg
oldStartVnode = oldCh[++oldStartIdx]; newEndVnode = newCh[--newEndIdx];
The third while
The first and last four matches are not matched. Enter the code block.
- Create key to oldidx (oldch, oldstartidx, oldendidx) for the remaining oldVnode (see the figure below)
- If idxInOld does not exist, it means new element = > createElm
- If idxInOld exists = > oldCh[idxInOld] = undefined = > reference node executes for a node nodeOps.insertBefore
- At this time, the interface also starts from fabcdeg = > fdabceg
The fourth while
Successful comparison of the first aa
The fifth while
The comparison between the first and the last bb is successful. The reference node is g node nodeOps.insertBefore
fdabceg => fdacebg
Sixth while
The comparison of the first and last cc is successful. The reference node is b node nodeOps.insertBefore
fdacebg => fdaecbg
The seventh while
At this time, oldStartVnode === undefined = > oldStartVnode = oldCh[++oldStartIdx];
Eighth while
Tail ee matching succeeded
At this time, oldendidx < oldstartidx jumps out of while and enters the following code block.
Oldendidx < oldstartidx = > added node = > reference node executes addvnodes() for e node (addvnodes execution finally also nodeOps.insertBefore ), insert the remaining nodes (H) of newCh in front of the E (refElm) node.
Press F8 once again in debugger to complete the diff process. Now the final fdahecbg is displayed
summary
It is strongly recommended to use Chrome browser for debugging. The coordination diagram is not easy to understand
- The diff key function updateChildren of rendering list
- Note VNode structure = > < p > a < p > = > VNode{tag: 'p', children: VNode{tag: undefined, children: undefined, text: 'a'}, text: undefined}
- Each cycle: first = > last = > first = > last = > FindIndex = > createelm (! Idxinold)| nodeOps.insertBefore (idxInOld)
- Out of loop = > addvnodes (oldstartidx > oldendidx) | removevnodes (newstartidx > newendidx)
last
Note: if you want to learn to practice together with the actual combat, you can get the latest enterprise class Vue3.0/Js/ES6/TS/React/node and other real combat video tutorials in 2020. You can get them for free in skirt 519293536 if you want to learn, and you can't get them in white!
The text and pictures of this article come from the Internet and my own ideas. They are only for learning and communication. They have no commercial use. The copyright belongs to the original author. If you have any questions, please contact us in time for handling