This is the second day of my participation in Gwen Challenge
This paper mainly introduces the whole process of Vue patch, including diff algorithm and the process of creating new nodes
preface
Vue’s virtual DOM algorithm is based on Snabbdom library, which has good speed and module mechanism. Vue Diff uses double Pointers to update the DOM while comparing.
React mainly uses the DIff queue to save the DOM to be updated, obtain the patch tree, and then update the DOM in a unified batch.
Characteristics of diff
- Comparisons will only be made at the same level, not across.
- In diff, the cycle moves from side to center
Vue patch process
_update
Vue in mount instance, mountComponent method is a key function: _update, this function is a private Vue instance methods, its role is to paint Vnode in real DOM, defined in SRC/core/instance/lifecycle. Js, Internally, it mainly calls the vm. __Patch__ method
__patch__
Vm. __patch__ is directly pointing to the Vue. Prototype. __patch__, the browser environment, the __path__ method is a method that directly pointing to patch it defined in SRC/platforms/web/runtime/index in js
patch
This patch method is defined in SRC/platforms/web/runtime/patch. The js, it is very concise, is to call the createPatchFunction function return values
createPatchFunction
The createPatchFunction method is defined in SRC /core/vnode/patch.js, and patch.js as a whole exposes only one method createPatchFunction, which returns a patch function. This is where we come to the Vue Patch node.
Let’s take a closer look at what this createPatchFunction does:
The patch function
When Vue is in patch node, it will first determine whether the new and old nodes are of the same type. If sameVNode is false, it will directly destroy oldVnode and render newVnode. Otherwise, the patchVnode method is entered
patchVnode
Next look at the patchVnode method:
PatchVnode receives six parameters: oldVnode, vnode, insertdVnodeQueue, ownerArray, index, removeOnly
PatchVnode will first determine whether it is a text node. If it is a text node, it will be good to replace the text content directly. If it is not, it will start to compare children.
If only the new nodes have children, run addVnodes to add new child nodes
If only the old node has children, remove the old node by removeVnode
If both the old and new nodes have children, the updateChildren method is executed.
updateChildren
The diff procedure is implemented primarily in the updateChildren function.
- When virtual real DOM DOM rendering of the new and old VNode marked the beginning of the end position, oldStartIdx, newStartIdx, oldEndIdx, newEndIdx
- Once the node is marked, it enters the while loop, which exits if the starting position of the old node or the new node is greater than the ending position
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
...
}
Copy the code
- During the cycle, the first and last nodes of the old and new nodes are compared
Specific analysis:
First, when the new and old nodes start to meet the requirements of sameVnode, patchVnode is directly used. At the same time, the old and new nodes start to index +1
Then compare the end nodes of the old and new nodes. If sameVnode is true, patchVnode is also directly used. At the same time, the new and old nodes end index -1
Then the start node of the old node is compared to the end node of the new node. If sameVnode is true, oldStartVnode is moved to the nextSibling of oldEndVnode. PatchVnode of these two nodes is first implemented, and the real DOM node is moved to the back of oldEndVnode by insertBefore. The start index of the old node is +1, but since the old start node is moved behind the old end node, the corresponding end index of the new node needs -1
The last comparison is that the end node of the old node is compared with the start node of the new node, which satisfies sameVnode, indicating that oldEndVnode of the old node should be moved before oldStartVnode of the old node. After patchVnode of these two nodes, the current real DOM needs to be moved to the front of oldStartVnode, so corresponding to it, the end index of the old node should be -1, and the start index of the new node should be +1
- If none of the four comparisons between the beginning and end of the old and new nodes satisfy sameVnode, the node is searched for reusable nodes based on the key value.
If the new node has a key value, the system starts to search the hash table with oldVnode as the key and corresponding index as value. Otherwise, the search is traversed through the entire tree of old nodes. This also illustrates the importance of defining key values.
Find oldVnode with the same key as newStartVnode from the hash table. If it meets sameVnode, patchVnode is used. At the same time, move the real DOM node to the front of the real DOM corresponding to oldStartVnode.
If newVnode does not exist in the oldVnode queue, call createElm to create a new DOM node in the same location as newStartIdx.
Map table generating functions:
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
Copy the code
If no key value is defined for the new node, the search starts from the start position of the current old node
function findIdxInOld (node, oldCh, start, end) {
for (let i = start; i < end; i++) {
const c = oldCh[i]
if (isDef(c) && sameVnode(node, c)) return i
}
}
Copy the code
- After the loop is complete, add or delete nodes according to the number of old and new nodes. If the number of new nodes is greater than the number of old nodes, the extra nodes are created and added to the real DOM; otherwise, the extra nodes are deleted from the real DOM.
How to determine whether two nodes can be reused
Vnode is defined in SRC /core/vdom/vnode.js.
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;
fnContext: Component | void; // real context vm for functional nodesfnOptions: ? ComponentOptions;// for SSR caching
devtoolsMeta: ?Object; // used to store functional render context for devtoolsfnScopeId: ? string;// functional scope id support
constructor(){... }}Copy the code
sameVnode
The sameVnode function is used to determine whether two points can be reused. First, the key of the old node and the new node must be the same. Second, the label must be the same. The input tag makes a special judgment
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 the code
sameInputType
function sameInputType (a, b) {
if(a.tag ! = ='input') return true
let i
const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type
const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type
return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
}
Copy the code
Take a look at the definitions of several utility functions: / SRC /shared/utils
export function isUndef (v: any) :boolean %checks {
return v === undefined || v === null
}
export function isDef (v: any) :boolean %checks {
returnv ! = =undefined&& v ! = =null
}
Copy the code
Refer to && recommended articles
Vue Diff algorithm parsing
Why doesn’t Vue use index as the key