preface
Vue source virtual DOM and Diff algorithm of the realization of the reference to snabbDOM this library, SNabbDOM is a virtual DOM library, it focuses on simple, modular, powerful functions and performance. In order to understand the virtual DOM and Diff algorithms thoroughly, we have to analyze what does snabbDOM do?
Get the source code
The snabbDOM library can be downloaded via NPM I snabbdom -d to see both SRC source Typescript and compiled JavaScript code. The source code posted below is version 2.1.0 and has been updated to version 3.0.3. It is recommended that you copy the source code shown below into the snabbDOM library so that you can see the source code clearly. Let’s start analyzing the source code.
Source code analysis
JavaScript objects simulate real DOM trees
The actual DOM nodes can be abstracted by calling the H function in the SNabbDOM library. Let’s take a look at what a full virtual DOM node (vNode) looks like:
{
sel: "div".// The current vNode selector
elm: undefined.// The current vNode corresponds to the actual DOM node
key: undefined.// Unique identifier of the current VNode
data: {}, // Current vNode attributes, styles, etc
children: undefined.// The child element of the current vNode
text: 'Text content' // The current vNode text node content
}
Copy the code
In fact,h
The DOM () function abstracts the real DOM tree by simulating it with JavaScript objects. callh
Function to get a virtual DOM tree made up of vNodes.callh
Functions take many forms:
(1) h ('div') (2) h ('div'.'text') (3) h ('div', h('p'(4) h ())'div'H, []) (5) ('div', 6 h ({})'div', {}, 'text'() 7 h'div', {}, h('span'H) today ('div', {}, [])
Copy the code
Make h function of the second and third parameters more flexible, to judge the situation is also more, the following part of the core source code analysis paste:
// h function: Based on the parameters passed in, the call form of h function and the corresponding attribute value of each vNode attribute can be inferred
export function h(sel: string) :VNode
export function h(sel: string, data: VNodeData | null) :VNode
export function h(sel: string, children: VNodeChildren) :VNode
export function h(sel: string, data: VNodeData | null, children: VNodeChildren) :VNode
export function h(sel: any, b? : any, c? : any) :VNode {
var data: VNodeData = {};
var children: any;
var text: any;
var i: number
// c = ⑥ ⑦ ⑧
if(c ! = =undefined) {
// if c has a value, b has a value (⑥ ⑦ ⑧)
if(b ! = =null) {
// Assign b to data
data = b
}
// the data type of c is array
if (is.array(c)) {
children = c
// check that c is a text node
} else if (is.primitive(c)) {
text = c
{sel: 'span'}} {sel: 'span'}}
// then c has a value and c has sel property
} else if (c && c.sel) {
{sel: 'span'}} {sel: 'span'}}
// Finally assemble the array and assign values to children
children = [c]
}
// c has no value, b has value, ② ③ ④ ⑤
} else if(b ! = =undefined&& b ! = =null) {
// the data type of b is array
if (is.array(b)) {
children = b
// Check that b is a text node
} else if (is.primitive(b)) {
text = b
{sel: 'p'}} {sel: 'p'}}
// then b has a value and b has sel property
} else if (b && b.sel) {
{sel: 'p'}} {sel: 'p'}}
// Finally assemble the array and assign values to children
children = [b]
⑤, assign b to data
} else { data = b }
}
// children has a value, traversing children
if(children ! = =undefined) {
for (i = 0; i < children.length; ++i) {
// Call vnode to check whether each item in children is of type string/number
if (is.primitive(children[i])) {
children[i] = vnode(undefined.undefined.undefined, children[i], undefined)}}}* {sel: 'div', * data: {style: '#000'}, * children: undefined, * text: 'text', * elm: Undefined, * key: undefined *} * such JavaScript objects */
return vnode(sel, data, children, text, undefined);
}
Copy the code
// The vNode function assembles the vNode structure from the parameters passed in
export function vnode(sel: string | undefined,
data: any | undefined,
children: Array<VNode | string> | undefined,
text: string | undefined,
elm: Element | Text | undefined) :VNode {
// Check whether data has a value. If data has a value, assign data.key to key; if no value, assign undefined to key
const key = data === undefined ? undefined : data.key
// Assemble the arguments passed to the vNode function into an object
return { sel, data, children, text, elm, key }
}
Copy the code
Diff algorithm — entry function
After obtaining the old and new virtual node DOM objects through the h function, the differences can be compared. In actual use, we directly call the patch function of SNabbDOM and pass in two parameters. The difference between the old and new virtual node DOM objects can be obtained through internal processing of the patch function, and the difference part can be updated to the real DOM tree.
First, the patch function will determine whether oldVnode is a real DOM node. If so, it needs to be converted to a virtual DOM node first. Then compare whether the old and new vnodes are the same node sameVnode(oldVnode, Vnode). If it is the same node, compare patchVnode(oldVnode, VNode, insertedVnodeQueue) accurately. CreateElm (vNode, insertedVnodeQueue) createElm(vnode, insertedVnodeQueue) createElm(vnode, insertedVnodeQueue) And insert the child node into the corresponding position. If oldvNode. elm has a parent node, insert the real DOM node corresponding to the new vnode as the child node and delete the old node. Here is the source code analysis of patch function:
function patch(oldVnode: VNode | Element, vnode: VNode) :VNode {
let i: number, elm: Node, parent: Node
const insertedVnodeQueue: VNodeQueue = []
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]()
// isVnode(oldVnode) Checks whether oldvNode. sel exists. If no, oldVnode is a real DOM node
if(! isVnode(oldVnode)) {// oldVnode can be a real DOM node or an old virtual DOM node,
// If it is a real DOM node, call the vNode function to assemble the virtual DOM node
oldVnode = emptyNodeAt(oldVnode)
}
// It is the same virtual DOM node
if (sameVnode(oldVnode, vnode)) {
// Compare two virtual DOM nodes exactly
patchVnode(oldVnode, vnode, insertedVnodeQueue)
} else {
// oldvNode. elm is the real DOM node corresponding to the virtual DOM node
elm = oldVnode.elm!
ParentNode (elm) Gets the parent elm elm. ParentNode
parent = api.parentNode(elm) as Node
// Create a real DOM node under vNode and update it to the corresponding location
createElm(vnode, insertedVnodeQueue)
The parent node of elm exists
if(parent ! = =null) {
// api.nextsibling (elm)-->elm. NextSibling returns the node following elm
// api.insertbefore (parent, B, C)-->--> parent.insertbefore (B, C)api.insertBefore(parent, vnode.elm! , api.nextSibling(elm)) removeVnodes(parent, [oldVnode],0.0) // Remove the old DOM node}}for (i = 0; i < insertedVnodeQueue.length; ++i) { insertedVnodeQueue[i].data! .hook! .insert! (insertedVnodeQueue[i]) }for (i = 0; i < cbs.post.length; ++i) cbs.post[i]()
return vnode
}
Copy the code
Patch function uses emptyNodeAt function, which mainly deals with the situation where the first parameter of patch function is a real DOM node. Here is the source code for this function:
function emptyNodeAt(elm: Element) {
// check whether elm has an id attribute, because sel attribute of the virtual DOM is a selector, such as div#wrap
const id = elm.id ? The '#' + elm.id : ' '
// Check whether the passed ODM elm has a class attribute, because the sel attribute of the virtual DOM node is a selector, for example, div.wrap
const c = elm.className ? '. ' + elm.className.split(' ').join('. ') : ' '
// Call the vnode function to assemble the passed DOM nodes into virtual DOM nodes
return vnode(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm)
}
Copy the code
The patch function uses the sameVnode function, which is mainly used to compare whether two virtual DOM nodes are the same virtual node. Here is the source analysis of this function:
function sameVnode(vnode1: VNode, vnode2: VNode) :boolean {
// Check whether vnode1 and vnode2 are the same virtual DOM node
return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel
}
Copy the code
Diff algorithm – The old and new VNodes are not the same node
According to the result returned by the sameVnode function, the old and new VNodes are not the same virtual node. Call createElm to create vNode’s real DOM node, its children, tag attributes, and so on. Check whether there is a parent node. If so, insert the DOM node corresponding to vnode.elm as a child node into the corresponding position under the parent node. CreateElm (patch); createElm (patch);
function createElm(vnode: VNode, insertedVnodeQueue: VNodeQueue) :Node {
let i: any
let data = vnode.data
if(data ! = =undefined) {
constinit = data.hook? .initif (isDef(init)) {
init(vnode)
data = vnode.data
}
}
const children = vnode.children
const sel = vnode.sel
// check whether sel value contains!
if (sel === '! ') {
if (isUndef(vnode.text)) {
vnode.text = ' '
}
// --> document.createComment(vnode.text!) Creating a comment node
vnode.elm = api.createComment(vnode.text!)
} else if(sel ! = =undefined) {
// Parse sel selectors
// select * from sel, return -1
const hashIdx = sel.indexOf(The '#')
// use hashIdx as the starting position to find sel property values. If hashIdx < 0 then start the search at position 0
const dotIdx = sel.indexOf('. ', hashIdx)
const hash = hashIdx > 0 ? hashIdx : sel.length
const dot = dotIdx > 0 ? dotIdx : sel.length
// If an ID or class selector exists, the tag name is truncated from bit 0 to the lowest index value
// Select sel
consttag = hashIdx ! = = -1|| dotIdx ! = = -1 ? sel.slice(0.Math.min(hash, dot)) : sel
// Create a DOM element based on the tag name
const elm = vnode.elm = isDef(data) && isDef(i = data.ns)
? api.createElementNS(i, tag)
: api.createElement(tag)
// Set the id attribute
if (hash < dot) elm.setAttribute('id', sel.slice(hash + 1, dot))
// Set the calss property
if (dotIdx > 0) elm.setAttribute('class', sel.slice(dot + 1).replace(/\./g.' '))
for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode)
// Check if children is an array
if (is.array(children)) {
for (i = 0; i < children.length; ++i) {
const ch = children[i]
if(ch ! =null) {
CreateElm (ch as VNode, insertedVnodeQueue) creates child nodes recursively
AppendChild (A, B)--> a.appendChild (B) inserts B at the end of the list of children of the specified parent node A
api.appendChild(elm, createElm(ch as VNode, insertedVnodeQueue))
}
}
// Check whether vnode.text has a value
} else if (is.primitive(vnode.text)) {
// api.createTextNode(vnode.text) Creates a text node based on vnode.text
AppendChild (elm, B)--> a.appendChild (B) adds the text node B to the end of the list of children of the parent node ELM
api.appendChild(elm, api.createTextNode(vnode.text))
}
consthook = vnode.data! .hookif(isDef(hook)) { hook.create? .(emptyNode, vnode)if (hook.insert) {
insertedVnodeQueue.push(vnode)
}
}
} else {
// sel does not exist to create text nodes directly
vnode.elm = api.createTextNode(vnode.text!)
}
return vnode.elm
}
Copy the code
Diff algorithm – The old and new VNodes are the same node
The above analysis shows that the old and new vNodes are not the same virtual node. What should we do if they are the same virtual node? First, the patchVnode function patchVnode(oldVnode, vnode, insertedVnodeQueue) is called. This function makes an exact comparison between the old and new vNodes:
(1) If oldVnode === vnode is identical to the old and new virtual DOM objects, then no operation is performed.
IsUndef (oldCh) &&isdef (ch); oldVnode (oldCh) &&isdef (ch); If both have children and are not equal, the updateChildren function is called to update the children.
③ If only VNode has child nodes and oldVnode has text nodes or no content, empty the text node of oldVnode or do no processing, call addVnodes function to create the corresponding real DOM of vnode’s child nodes and insert it into the parent node.
④ If only oldVnode has child nodes and vnode has no content, delete the child nodes under oldVnode directly.
⑤ If only oldVnode has text nodes and Vnode has no content, the text of the real DOM node corresponding to oldVnode is null;
⑥ If the vnode has a text node, oldVnode has a child node corresponding to the real DOM node child node delete, no processing, and then vnode text node as a child node into the corresponding real DOM node.
In the patch function, the source analysis of patchVnode function is posted below:
function patchVnode(oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue) {
consthook = vnode.data? .hook hook? .prepatch? .(oldVnode, vnode)const elm = vnode.elm = oldVnode.elm!
const oldCh = oldVnode.children as VNode[]
const ch = vnode.children as VNode[]
// oldVnode is completely equal to vNode and there is no content to update
if (oldVnode === vnode) return
if(vnode.data ! = =undefined) {
for (let i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode) vnode.data.hook? .update? .(oldVnode, vnode) }// vnode.text undefined indicates that the vNode virtual node has no text content
if (isUndef(vnode.text)) {
// If oldCh and ch are not undefined, oldVnode and vnode have virtual children
if (isDef(oldCh) && isDef(ch)) {
// oldCh ! == CH uses the algorithm to update child nodes
if(oldCh ! == ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue) }else if (isDef(ch)) {
// Set the oldVnode text node to ""
if (isDef(oldVnode.text)) api.setTextContent(elm, ' ')
// Call the addVnodes method to loop vNode's virtual child nodes into the elm node's child list
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
// If oldCh is not undefined, oldVnode has virtual children
} else if (isDef(oldCh)) {
Delete children from oldVnode if vnode has no children
removeVnodes(elm, oldCh, 0, oldCh.length - 1)
// oldvNode. text has a value and vnode.text has no value
} else if (isDef(oldVnode.text)) {
// Set the oldVnode text node to ""
api.setTextContent(elm, ' ')}// oldVnode and vNode text nodes have different contents
} else if(oldVnode.text ! == vnode.text) {// isDef(oldCh)-->oldCh ! == undefined indicates that the oldVnode virtual node has virtual child nodes
if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1)}// oldCh virtual node has no virtual children to update the text content directlyapi.setTextContent(elm, vnode.text!) } hook? .postpatch? .(oldVnode, vnode) }Copy the code
Diff algorithm — Update strategy for old and new VNode child nodes
When both old and new VNodes have child nodes,diff
The algorithm defines four Pointers to handle child nodes, which are:OldStartVnode vnode before (old)
/NewStartVnode vnode before (new)
/OldEndVnode vnode after (old)
/NewEndVnode vnode after (new)
. After entering the circulatory body, the child nodes of the old and new VNodes are compared in pairs, which provides a set of comparison rules as shown below:If none of the above rules is met, remove the children of oldVnode from the old pre-indexoldStartIdx
To the old back indexoldEndIdx
Make a mapping between the key and the sequence number of the corresponding positionoldKeyToIdx
, using the key of the new vNodeoldKeyToIdx
Is there a corresponding index value in. If no, indicateoldVnode
There is no corresponding old node, but a new node is inserted. If yes, indicateoldVnode
There is a corresponding old node, not a new node, to move the operation. When all the children of one of the old and new VNodes are processed, but the other is not, a different processing scheme is used. Specific look at the following source code analysis:
OldEndIdx = oldEndIdx; // oldEndIdx = oldEndIdx; // oldEndIdx = oldEndIdx
function createKeyToOldIdx(children: VNode[], beginIdx: number, endIdx: number) :KeyToIndexMap {
const map: KeyToIndexMap = {}
for (let i = beginIdx; i <= endIdx; ++i) {
constkey = children[i]? .keyif(key ! = =undefined) {
map[key] = i
}
}
/** * Example: map = {A: 1, B: 2} */
return map
}
Copy the code
function updateChildren(parentElm: Node, oldCh: VNode[], newCh: VNode[], insertedVnodeQueue: VNodeQueue) {
let oldStartIdx = 0 // Old index
let newStartIdx = 0 // New pre-index
let oldEndIdx = oldCh.length - 1 // Old post-index
let newEndIdx = newCh.length - 1 // New post-index
let oldStartVnode = oldCh[0] // The old former vNode
let newStartVnode = newCh[0] // New former vNode
let oldEndVnode = oldCh[oldEndIdx] // Old post-vNode
let newEndVnode = newCh[newEndIdx] // New post-vNode
let oldKeyToIdx: KeyToIndexMap | undefined
let idxInOld: number
let elmToMove: VNode
let before: any
// Execute the loop when the old pre-index <= the old post-index && the new pre-index <= the new post-index
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
// Why oldStartVnode == null?
// The original virtual node will be set to undefined after the virtual node is moved
// oldCh[idxInOld] = undefined as any
if (oldStartVnode == null) {
If oldStartVnode is null, filter out the current node. OldCh [++oldStartIdx] is the next node of the old former index.
oldStartVnode = oldCh[++oldStartIdx]
} else if (oldEndVnode == null) {
OldCh [--oldEndIdx] oldCh[--oldEndIdx] oldCh[--oldEndIdx]
oldEndVnode = oldCh[--oldEndIdx]
} else if (newStartVnode == null) {
// If newStartVnode is null, filter the current node and select newCh[++newStartIdx] node (the node next to the new pre-index).
newStartVnode = newCh[++newStartIdx]
} else if (newEndVnode == null) {
// If newEndVnode is null, filter out the current node and select newCh[--newEndIdx].
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
/** * ① Check whether the old former vnode (oldStartVnode) and the new former vnode (newStartVnode) are the same virtual node * old virtual child node new virtual child node * h('li', {key: 'A' }, 'A') h('li', { key: 'A' }, 'A') * h('li', { key: 'B' }, 'B') h('li', { key: 'B' }, 'B') */
// If it is the same virtual node, call patchVnode
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
// oldCh[++oldStartIdx] takes the next virtual node of the old former index node (the node whose key is B in the example) and assigns it to oldStartVnode
oldStartVnode = oldCh[++oldStartIdx]
// oldCh[++oldStartIdx] takes the next virtual node of the new former index node (the node with key B in this example) and assigns it to newStartVnode
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
/** * If the old old vnode (the virtual node with key B in the example) and the new old vnode (the virtual node with key B in the example) * are not the same virtual node, then compare the scheme ② * ② old old vnode (oldEndVnode) and the new old vnode (oldEndVnode) New vnode (newEndVnode) compares whether it is the same virtual node * old virtual child node New virtual child node * h('li', {key: 'C'}, 'C') h('li', {key: 'A' }, 'A') * h('li', { key: 'B' }, 'B') h('li', { key: 'B' }, 'B') */
// If it is the same virtual node, call patchVnode
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
// oldCh[--oldEndIdx] takes the last virtual node of the old post-index node (the virtual node whose key is C in this example) and assigns it to oldEndVnode
oldEndVnode = oldCh[--oldEndIdx]
// newCh[--newEndIdx] takes the last virtual node of the new index node (in this example, the virtual node with key A) and assigns it to newEndVnode
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) {
/** * If the old vnode and the new vnode are not the same virtual node, compare the old former vnode (oldStartVnode) and the new new vnode (newEndVnode) to see if they are the same virtual node * old virtual child node The new virtual child node * h (' li ', {key:} 'C', 'C') h (' li ', {key: 'A'}, 'A') * h (' li ', {key: 'B'}, 'B') h (' li '{key: 'B' }, 'B') * h('li', { key: 'C' }, 'C') */
// If it is the same virtual node, call patchVnode
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
// Insert the old former vnode (equivalent to the virtual node with key C in the example) before the next sibling of the current old post-vnode
// If oldEndVnode is the last virtual node, node.nextsibling will return null,
// The new virtual node is inserted directly at the end, equivalent to the appenChildapi.insertBefore(parentElm, oldStartVnode.elm! , api.nextSibling(oldEndVnode.elm!) )// oldCh[++oldStartIdx] takes the next virtual node (the virtual node whose key is B in this example) of the old former index virtual node and assigns it to oldStartVnode
oldStartVnode = oldCh[++oldStartIdx]
// newCh[--newEndIdx] takes the last virtual node of the new index virtual node (the virtual node whose key is B in this example) and assigns it to newEndVnode
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
/** * If the old former Vnode and the new former Vnode are not the same virtual node, check whether the old former Vnode (oldEndVnode) and the new former Vnode (newStartVnode) are the same virtual node * old virtual child node New virtual child node * h (' li ', {key:} 'C', 'C') h (' li ', {key:} 'B', 'B') * h (' li ', {key: 'B'}, 'B') h (' li '{key: 'A' }, 'A') * h('li', { key: 'C' }, 'C') */
// If it is the same virtual node, call patchVnode
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
// Insert the old post-vNode (key B in this example) before the current old pre-vNode (key C in this example)api.insertBefore(parentElm, oldEndVnode.elm! , oldStartVnode.elm!)// oldCh[--oldEndIdx] takes the last virtual node of the old post-index node (the virtual node whose key is C in this example) and assigns it to oldEndVnode
oldEndVnode = oldCh[--oldEndIdx]
// newCh[++newStartIdx] takes the next virtual node (the virtual node whose key is A in this example) of the new former index node and assigns it to newStartVnode
newStartVnode = newCh[++newStartIdx]
} else {
// Do not satisfy the above four conditions
if (oldKeyToIdx === undefined) {
// oldKeyToIdx saves the mapping between the key of each node and the sequence number of the corresponding position in the old children
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
}
// Obtain the serial number corresponding to the current newStartVnode key from oldKeyToIdx
idxInOld = oldKeyToIdx[newStartVnode.key as string]
if (isUndef(idxInOld)) { // isUndef(idxInOld) --> idxInOld === undefined
/** * idxInOld = undefined * The old virtual child node does not have a node corresponding to idxInOld, but the new virtual child node does. * so newStartVnode is A virtual node to be inserted * old virtual child nodes of the new virtual child node * h (' li ', {key: 'A'}, 'A') h (' li ', {key:} 'C', 'C') * h (' li '{key: 'B' }, 'B') */
// Create a real DOM node createElm() based on newStartVnode (the virtual node whose key is C in this example).
// Insert the created DOM node in front of oldStartvNode. elm (the node with key A in this example)
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm!)
} else {
/** * idxInOld ! = undefined * oldCh[idxInOld] is the virtual node to be moved * old virtual child node new virtual child node * h('li', {key: 'A' }, 'A') h('div', { key: 'B' }, 'B') * h('li', { key: 'B' }, 'B') h('li', { key: 'D' }, 'D') */
elmToMove = oldCh[idxInOld] // elmToMove saves the virtual node to be moved
// Check whether the SEL attribute of elmToMove is the same as that of newStartVnode when the key is the same
if(elmToMove.sel ! == newStartVnode.sel) {// Different sel attributes indicate different virtual nodes.
// Create A real DOM node from the newStartVnode virtual node and insert it before oldStartvNode. elm (the old node with key A)
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm!)
} else {
// If the key is the same as sel, it is the same virtual node. Call patchVnode
patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
// Set oldCh[idxInOld] to undefined after the virtual node is moved to filter out the processed nodes in the next loop
oldCh[idxInOld] = undefined as any
Elmtomove. elm (the old node with key B in the example) is inserted before oldStartvNode. elm (the node with key A in the example)
api.insertBefore(parentElm, elmToMove.elm!, oldStartVnode.elm!)
}
}
// Take the newCh[++newStartIdx] virtual node (the virtual node whose key is D in this example) and assign it to newStartVnode
newStartVnode = newCh[++newStartIdx]
}
}
/ * * * end of the cycle index before the old < = the old index after | | before the new index < = new index after, * says some virtual node (virtual node example key for C) didn't handle * old virtual child nodes is a new virtual child node * : * h (' li '{key: 'A' }, 'A') h('li', { key: 'A' }, 'A') * h('li', { key: 'B' }, 'B') h('li', { key: 'B' }, 'B') * h('li', { key: 'D'}, 'D') h (' li ', {key:} 'C', 'C') * h (' li ', {key: 'D'}, 'D') * case 2: * h (' li ', {key: 'A'}, 'A') h (' li '{key: 'A' }, 'A') * h('li', { key: 'B' }, 'B') h('li', { key: 'B' }, 'B') * h('li', { key: 'C' }, 'C') */
if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) {
// Handle case 1 of the example
if (oldStartIdx > oldEndIdx) {
NewCh [newEndIdx] is the virtual node whose key is C in the new child node.
// so before = newCh[newEndIdx + 1] is a virtual node with key D
before = newCh[newEndIdx + 1] = =null ? null : newCh[newEndIdx + 1].elm
// In this example newStartIdx and newEndIdx are both 2
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else {
// Delete the node between the old pre-index and the old post-index (delete the old virtual node with key C)
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}
}
Copy the code