Small knowledge, big challenge! This paper is participating in theEssentials for programmers”Creative activities.
TIP 👉 A man who has no foresight will have immediate concern. The Analects of Confucius · Duke Wei Ling
VNode
– A VNode is a Virtual node that describes a DOM element. If the VNode has children it is a Virtual DOM – SRC /package/vnode.ts
export interface VNode {
/ / selector
sel: string | undefined;
// Node data: attributes/styles/events etc
data: VNodeData | undefined;
// Child nodes, and text are mutually exclusive
children: Array<VNode | string> | undefined;
// Record the actual DOM corresponding to the vNode
elm: Node | undefined;
// The contents of this node are mutually exclusive with children
text: string | undefined;
/ / optimization
key: Key | undefined;
}
export function vnode (sel: string | undefined,
data: any | undefined,
children: Array<VNode | string> | undefined,
text: string | undefined,
elm: Element | Text | undefined) :VNode {
const key = data === undefined ? undefined : data.key
return { sel, data, children, text, elm, key }
}
Copy the code
patch
-
Function:
-
Pass in the old and new VNodes, compare the differences, and render the differences into the DOM
-
Return the new VNode as the oldVnode for the next patch()
-
-
Execution process:
-
Start by executing the hook function pre in the module
-
If oldVnode and VNode are the same (same key and SEL)
- Call patchVnode() to find the node differences and update the DOM
-
If oldVnode is a DOM element
-
Convert DOM elements into OLdvNodes
-
Call createElm() to convert vNode to a real DOM and log to vNode.elm
-
Insert the newly created DOM element into the parent
-
Removing an old node
-
Triggers the user-set CREATE hook function
-
-
return function patch (oldVnode: VNode | Element, vnode: VNode) :VNode {
let i: number, elm: Node, parent: Node
// Save the queue of newly inserted nodes in order to trigger the hook function
const insertedVnodeQueue: VNodeQueue = []
// Execute the module's Pre hook function
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]()
// If oldVnode is not VNode, create VNode and set elm
if(! isVnode(oldVnode)) {// Convert the DOM element to an empty VNode
oldVnode = emptyNodeAt(oldVnode)
}
// If the new and old nodes are the same node (key and SEL are the same)
if (sameVnode(oldVnode, vnode)) {
// Find the node differences and update the DOM
patchVnode(oldVnode, vnode, insertedVnodeQueue)
} else {
// If the old and new nodes are different, vNode creates the corresponding DOM
// Get the current DOM element
elm = oldVnode.elm!
parent = api.parentNode(elm) as Node
// Trigger init/create hook function to create DOM
createElm(vnode, insertedVnodeQueue)
if(parent ! = =null) {
// If the parent node is not empty, insert the DOM corresponding to the vnode into the documentapi.insertBefore(parent, vnode.elm! , api.nextSibling(elm))// Remove the old node
removeVnodes(parent, [oldVnode], 0.0)}}// Execute the user-set insert hook function
for (i = 0; i < insertedVnodeQueue.length; ++i) { insertedVnodeQueue[i].data! .hook! .insert! (insertedVnodeQueue[i]) }// Execute the module's POST hook function
for (i = 0; i < cbs.post.length; ++i) cbs.post[i]()
return vnode
}
Copy the code
patchVnode
-
Function:
-
patchVnode(oldVnode, vnode, insertedVnodeQueue)
-
Compare oldVnode and VNode differences and render the differences into the DOM
-
-
Execution process:
-
The user-set prePatch hook function is first executed
-
Execute the CREATE hook function
-
The module’s CREATE hook function is first executed
-
The user-set CREATE hook function is then executed
-
-
If vnode.text is not defined
-
If oldvNode. children and vnode.children both have values
-
Call updateChildren ()
-
Diff algorithm is used to compare and update child nodes
-
-
If vnode.children has a value, oldvNode. children has no value
-
Clearing DOM elements
-
Call addVnodes() to add child nodes in batches
-
-
If oldvNode. children has a value, vnode.children has no value
- call
removeVnodes()
To remove child nodes in batches
- call
-
If oldvNode. text has a value
- Empty the content of the DOM element
-
If vnode.text is set and differs from oldvNode. text
-
If the old node has children, remove them all
-
Set the textContent of the DOM element to vNode.text
-
-
Finally, execute the user-set PostPatch hook function
-
-
function patchVnode (oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue) {
consthook = vnode.data? .hook// Execute the user-set prepatch hook function firsthook? .prepatch? .(oldVnode, vnode)const elm = vnode.elm = oldVnode.elm!
const oldCh = oldVnode.children as VNode[]
const ch = vnode.children as VNode[]
// Return if the old and new vNodes are the same
if (oldVnode === vnode) return
if(vnode.data ! = =undefined) {
// Execute the module's update hook function
for (let i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
// Execute the user-set update hook functionvnode.data.hook? .update? .(oldVnode, vnode) }// If vnode.text is not defined
if (isUndef(vnode.text)) {
// If both new and old nodes have children
if (isDef(oldCh) && isDef(ch)) {
// Call updateChildren to compare and update child nodes
if(oldCh ! == ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue) }else if (isDef(ch)) {
// If the new node has children, the old node has no children
// Empty the dom element if the old node has text
if (isDef(oldVnode.text)) api.setTextContent(elm, ' ')
// Add child nodes in batches
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
// If the old node has children, the new node has no children
// Remove child nodes in batches
removeVnodes(elm, oldCh, 0, oldCh.length - 1)}else if (isDef(oldVnode.text)) {
// If the old node has text, clear the DOM element
api.setTextContent(elm, ' ')}}else if(oldVnode.text ! == vnode.text) {// If vnode.text is not set
if (isDef(oldCh)) {
// If the old node has children, remove it
removeVnodes(elm, oldCh, 0, oldCh.length - 1)}// Set the DOM element's textContent to vnode.text
api.setTextContent(elm, vnode.text!)
}
// Finally execute the user-set postpatch hook functionhook? .postpatch? .(oldVnode, vnode) }Copy the code
updateChildren
-
Function:
- The core of the diff algorithm, comparing the children of the old and new nodes, updates the DOM
-
Execution process:
-
To compare the difference between two trees, we can take each node of the first tree and compare it to each node of the second tree, but in order of n^3 time.
-
In DOM manipulation we rarely move/update a parent node to a child node
-
So you just need to compare the nodes at the same level, and then compare the nodes at the next level, so the time complexity of the algorithm is O(n).
-
-
When comparing nodes of the same level, the start and end nodes of the new and old node arrays will be marked with index, and the index will be moved during traversal
-
When comparing the start and end nodes, there are four cases
-
OldStartVnode/newStartVnode (old start node/new start node)
-
OldEndVnode/newEndVnode (old end node/new end node)
-
OldStartVnode/oldEndVnode (old start node/new end node)
-
OldEndVnode/newStartVnode (old end node/new start node)
-
-
The start node is compared to the end node. The two cases are similar
-
OldStartVnode/newStartVnode (old start node/new start node)
-
OldEndVnode/newEndVnode (old end node/new end node)
-
-
If oldStartVnode and newStartVnode are samevNodes (same key and SEL)
-
Call patchVnode() to compare and update nodes
-
Move the old start and new start indexes back oldStartIdx++ / oldEndIdx++
-
- OldStartVnode/newEndVnode (old start node/new end node) same
– Call patchVnode() to compare and update nodes
- Move the oldStartVnode DOM element to the right
– Update index
-
OldEndVnode/newStartVnode (old end node/new start node) same
-
Call patchVnode() to compare and update nodes
-
Move the oldEndVnode DOM element to the left
-
Update the index
-
If it’s not four of the above
-
Iterate over the new node, using the newStartNode key to find the same node in the old node array
-
If not, newStartNode is the new node
-
Create a DOM element corresponding to the new node and insert it into the DOM tree
-
If we find it,
-
Determine whether the SEL selector of the new node is the same as that of the old node found
-
If they are different, the nodes are modified
-
Re-create the corresponding DOM element and insert it into the DOM tree
-
If so, move the DOM element corresponding to elmToMove to the left
-
-
End of the cycle
-
The loop ends when all children of the old node are traversed first (oldStartIdx > oldEndIdx)
-
All children of the new node are traversed first (newStartIdx > newEndIdx), and the loop ends
-
If the array of the old node is traversed first (oldStartIdx > oldEndIdx), the new node has a surplus, and the remaining nodes are batch inserted to the right
- If the array of the new node is traversed first (newStartIdx > newEndIdx), it indicates that the old node has surplus. Delete the remaining nodes in batches