Next comes the update process
When the responsive properties are modified, the subscribed Watcher is notified of updates, triggering the component to rerender; First, execute the component render function to get the component VNode, and then execute _update
Vue.prototype._update = function (vnode: VNode, hydrating? : boolean) {
const vm: Component = this
const prevEl = vm.$el / / the dom node
// Obtain the VNode before the update
const prevVnode = vm._vnode
// Set activeInstance and return an anonymous function that returns the value of the previous activeInstance
const restoreActiveInstance = setActiveInstance(vm)
// Vnode created by the render function of the current vue instance
vm._vnode = vnode
if(! prevVnode) { vm.$el = vm.__patch__(vm.$el, vnode, hydrating,false /* removeOnly */)}else {
/ / update
vm.$el = vm.__patch__(prevVnode, vnode)
}
// Set activeInstance to the value of the previous VM instance
restoreActiveInstance()
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
}
Copy the code
PrevVnode has a value during the update compared to the first render. The value is the VNode before the update. So else logic is used. Else logic also calls vm.__patch__ but passes prevVnode. Note that VNode is set to the latest VNode;
Let’s look at the patch function
return function patch (oldVnode, vnode, hydrating, removeOnly) {
// The new node does not exist, the old node exists, destroy the old node
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
} else {
const isRealElement = isDef(oldVnode.nodeType)
if(! isRealElement && sameVnode(oldVnode, vnode)) { patchVnode(oldVnode, vnode, insertedVnodeQueue,null.null, removeOnly)
} else {
if (isRealElement) {
oldVnode = emptyNodeAt(oldVnode)
}
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
createElm(
vnode,
insertedVnodeQueue,
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// Update the vnode ELM and re-execute the parent component's CBs. create and INSERT hooks.
if (isDef(vnode.parent)) {
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
for (let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor)
}
// Update the VNode ELM
ancestor.elm = vnode.elm
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor)
}
const insert = ancestor.data.hook.insert
if (insert.merged) {
// Insert hook = mounted //
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
// destroy old node
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0.0)}else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
Copy the code
In the update process, the patch function will appear in three cases, which are:
- The current component is created for the first time. For example, the parent component passes
v-if
Controls whether the subcomponent is rendered - The new and old nodes are the same
- The new and old nodes are different
As for the first case, it’s the same as the first rendering process, so I won’t go into much detail here. The following case follows the first logic
<template>
<div>
<cmp1 v-if="xxx">xxx</cmp1>
<cmp2 v-else>yyy</cmp2>
</div>
</template>
Copy the code
When XXX is changed to false, cmp2 is created and oldVNode of Cmp2 is null. That is, if the component was mounted during the initial render, oldVNode will be present during the update phase, and vice versa
The second and third cases are based on the following logic
// Inside the patch function
// oldVnode is not a real node, and sameVnode returns true
if(! isRealElement && sameVnode(oldVnode, vnode)) { patchVnode(oldVnode, vnode, insertedVnodeQueue,null.null, removeOnly)
}
Copy the code
SameVnode function
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
- For synchronous components, if two
vnode
的key
If they are not equal, they are different; ifkey
The same continues to judgeisComment
,data
,input
The types and so on are the same - For asynchronous components, if two
vnode
的key
If they are not equal, they are different; ifkey
The same continues to judgeasyncFactory
Whether or not the same
The new and old nodes are the same
When!!!! IsRealElement && sameVnode(oldVnode, vNode) is set up and patchVnode is executed
function patchVnode (oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
if (oldVnode === vnode) {
return
}
// Assign oldvnode.elm to vnode.elm
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
}
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
// Update all attributes of the node
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(process.env.NODE_ENV ! = ='production') {
checkDuplicateKeys(ch)
}
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, ' ')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
removeVnodes(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)
}
}
Copy the code
The function of patchVnode is to update the ELM properties of VNode. In other words, the following process is actually the process of modifying the DOM tree
His logic is to return if the two nodes are the same. Elm is assigned to vnode.elm and the DOM tree is set for the new vnode. If VNode is a component placeholder VNode, execute VNode’s prepatch hook function to update the subcomponent. Then, get the children of the old and new nodes; Update all functions in CBS. Update and VNode’s update hook function are executed to update all properties of the node. The comparison then begins, and if the new node is a text node and the new and old text are different, the ELM text content is directly replaced. If the new vnodes are not text nodes, then their children are identified and treated in several ways:
- New and old nodes have child nodes, and child nodes are not the same, use
updateChildren
Function to update a child node - If only the new VNode has children, the old VNode is either a text node or has no children. If the old node is a text node clear the text of the node; Then through
addVnodes
Insert all child nodes of the new VNode into the new node in batcheselm
下 - If only the old VNode has child nodes, the new VNode is an empty node. Then all the child nodes of the old VNode pass through
removeVnodes
Remove all - When only the old node is a text node, its node text content is cleared
After that, the Postpatch hook function is executed
updateChildren
In the first example above, the updateChildren function is called when both the old and new nodes have children, and the children are different. Let’s take a look at how the updateChildren function updates a child node. This is actually a recursive procedure. The code is as follows.
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
constcanMove = ! removeOnlyif(process.env.NODE_ENV ! = ='production') {
checkDuplicateKeys(newCh)
}
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 {
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
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 the code
We first define four Pointers and the VNode nodes corresponding to the four Pointers
oldStartIdx
,oldEndIdx
,newStartIdx
,newEndIdx
These are the indexes on both sides of the new and old VnodesoldStartVnode
,oldEndVnode
,newStartVnode
,newEndVnode
Points to the VNode nodes corresponding to these indexes
And then a while loop, oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx; The while loop ends when the start index of the old node is greater than the end index of the old node or the start index of the new node is greater than the end index of the new node.
The logic inside the while loop is as follows
oldStartVnode
In the case of no,oldStartIdx
Move to the center and updateoldStartVnode
The value of theoldEndVnode
In the case of no,oldEndIdx
Move to the center and updateoldEndVnode
The value of theoldStartVnode
和newStartVnode
It’s the same node, which means the two nodes have the same beginning, callpatchVnode
To update the child node. After the child node is updated, theoldStartIdx
与newStartIdx
Move back one bitoldEndVnode
和newEndVnode
It’s the same node, it’s the same end of the two nodes, it’s the same processpatchVnode
Operation and will beoldEndIdx
与newEndVnode
Move forward one bitoldStartVnode
和newEndVnode
Is the same node, that is, the head of the old node and the tail of the new node are the same node, calledpatchVnode
, update the child node; After the child node update is complete, theoldStartVnode.elm
Move to theoldEndVnode.elm
The back; thenoldStartIdx
If I move back one bit,newEndIdx
Move forward one bit.
oldEndVnode
和newStartVnode
Is the same node, that is, the tail of the old node and the head of the new node are the same nodepatchVnode
, update the child node; After the child node update is complete, theoldEndVnode.elm
Inserted into theoldStartVnode.elm
The front;oldEndIdx
If I move forward one bit,newStartIdx
I’m going to move back one bit.
- If all of the above are lost, enter the following logic
else {
if (isUndef(oldKeyToIdx)) oldKeyToIdx = 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, 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 {
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
Copy the code
CreateKeyToOldIdx {[name of key]: [index in oldCh]}; If newStartVnode has a key, get the position of that key in oldCh and assign it to idxInOld; Otherwise, oldCh is traversed, looking for the same node as newStartVnode, and if found, the corresponding index is returned and assigned to idxInOld. The logic follows:
-
If idxInOld is undefined, all nodes in newStartVnode and oldCh are different. Call createElm to create the node and insert in front of oldStartVNode.elm. Set newStartIdx one bit later and update the value of newStartVnode
-
If idxInOld has a value, newStartVnode has the same or the same node in oldCh, gets this node, and again uses sameVnode to determine whether this node is the same as newStartVnode
- If the same call
patchVnode
Update the child node. When the child node is updated, this node is removed fromoldCh
, and willvnodeToMove.elm
(oldCh[key].elm
) into theoldStartVnode.elm
The front; letnewStartIdx
And updatenewStartVnode
The value of the - If not, call
createElm
Create a node and insert it intooldStartVnode.elm
The front. letnewStartIdx
And updatenewStartVnode
The value of the
- If the same call
The following logic is executed when the while loop ends
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 the code
- if
oldStartIdx
Is greater thanoldEndIdx
Note The old node is traversed first. And then determinenewCh[newEndIdx + 1]
Is there a value? If yes, it indicates that the remaining new nodes (newStartIdx
tonewEndIdx
Between nodes) should be inserted intonewCh[newEndIdx + 1].elm
The front; Conversely, insert at the end - if
newStartIdx
Is greater thannewEndIdx
Note The new node is traversed first. Directly to theoldStartIdx
tooldEndIdx
Delete all nodes between
Prepatch hook function
In the patchVnode method, if the new VNode is a component placeholder VNode, the prepatch hook function of the VNode is called
prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
// Select options from VNode
const options = vnode.componentOptions
// Get the component instance
const child = vnode.componentInstance = oldVnode.componentInstance
updateChildComponent(
child,
options.propsData, // Updated props specifies the latest props of the child component
options.listeners, // Updated Listeners Custom events
vnode, // new parent vnode
options.children // new children)}Copy the code
UpdateChildComponent is called within the Prepatch hook function, passing in the child component instance, the latest Prop data, custom events, the new VNode, and the child node (named slot with the name property specifying the slot content).
The updateChildComponent function looks like this
export function updateChildComponent (
vm: Component, // Subcomponent instance
propsData: ?Object,
listeners: ?Object,
parentVnode: MountedComponentVNode, / / component vnode
renderChildren: ?Array<VNode>
) {
if(process.env.NODE_ENV ! = ='production') {
// Set this parameter to true so that assignment to props[key] fires the set method without causing the customSetter function to give an error
isUpdatingChildComponent = true
}
const newScopedSlots = parentVnode.data.scopedSlots
const oldScopedSlots = vm.$scopedSlots
consthasDynamicScopedSlot = !! ( (newScopedSlots && ! newScopedSlots.$stable) || (oldScopedSlots ! == emptyObject && ! oldScopedSlots.$stable) || (newScopedSlots && vm.$scopedSlots.$key ! == newScopedSlots.$key) )constneedsForceUpdate = !! ( renderChildren ||// has new static slots
vm.$options._renderChildren || // has old static slots
hasDynamicScopedSlot
)
// vm.$options._parentVnode points to the new vNode
vm.$options._parentVnode = parentVnode
$vnode indicates the new component vnode
vm.$vnode = parentVnode // update vm's placeholder node without re-render
if (vm._vnode) { // update child tree's parent
// Update the render vNode parent
vm._vnode.parent = parentVnode
}
vm.$options._renderChildren = renderChildren
vm.$attrs = parentVnode.data.attrs || emptyObject
vm.$listeners = listeners || emptyObject
// update props
/ / update the props
if (propsData && vm.$options.props) {
toggleObserving(false)
// Previous propsData
const props = vm._props
// A collection of props properties defined by the child component
const propKeys = vm.$options._propKeys || []
for (let i = 0; i < propKeys.length; i++) {
const key = propKeys[i]
const propOptions: any = vm.$options.props // wtf flow?
// Change the props value here to trigger the component update
props[key] = validateProp(key, propOptions, propsData, vm)
}
toggleObserving(true)
vm.$options.propsData = propsData
}
// update listeners
listeners = listeners || emptyObject
// Get the custom event from the last binding
const oldListeners = vm.$options._parentListeners
// Assign this custom event to _parentListeners
vm.$options._parentListeners = listeners
updateComponentListeners(vm, listeners, oldListeners)
// resolve slots + force update if has children
if (needsForceUpdate) {
vm.$slots = resolveSlots(renderChildren, parentVnode.context)
vm.$forceUpdate()
}
if(process.env.NODE_ENV ! = ='production') {
// When the update is complete, set it to false
isUpdatingChildComponent = false}}Copy the code
When a VNode is updated, a series of properties of the VNode instance are changed, including vm.$VNode updates, slot updates, props updates, and so on. Updates to these properties trigger updates to the child component, which are described in the corresponding article and will not be covered here.
If the child component needs to be updated, then add the child component’s Render Watcher directly to the queue for execution, instead of calling nextTick, because queueWatcher’s flushing method is true, The child component’s Render Watcher is added to the queue at the correct location; Waiting is true, the nextTick method is not called.
After adding to the queue, go back to the patchVnode function and continue updating. When the parent component is updated, the child components are updated in the order in the queue.
The new and old nodes are different
When!!!! When isRealElement && sameVnode(oldVnode, vNode) fails, the following logic is executed
if(! isRealElement && sameVnode(oldVnode, vnode)) {// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, null.null, removeOnly)
} else {
if (isRealElement) {
// Create a vNode based on oldVnode (in this case oldVnode is a real node)
oldVnode = emptyNodeAt(oldVnode)
}
// Get the actual elements of the node
const oldElm = oldVnode.elm
// Obtain the parent node of oldVnode
const parentElm = nodeOps.parentNode(oldElm)
createElm(
vnode,
insertedVnodeQueue,
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// Update vnode elm and re-execute cbs.create and parent insert hooks.
if (isDef(vnode.parent)) {
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
for (let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor)
}
// Update the VNode ELM
ancestor.elm = vnode.elm
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor)
}
const insert = ancestor.data.hook.insert
if (insert.merged) {
// Insert hook = mounted //
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0.0)}else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
Copy the code
Create the node using createElm as the first time, or in the case of the component placeholder VNode, call the init hook function to create the component instance and perform the mounting process of the component. In the case of a normal VNode, create the node and call createChildren to create the child node, which is inserted into the current node. When this is done, insert the current node into the parent node.
Insert hook function (not including mounted hook function); delete the old node, return the latest DOM tree, and assign the value to vm.$el
The purpose of the insert hook is to prevent the following situation, if there is a custom directive on the component placeholder VNode, and the INSERT callback is bound to the DOM, if not updated, it will always be bound to the old DOM tree, so it needs to be updated
<template>
<div v-if="xxx">xxx</div>
<div v-else>yyy</div>
</template>
Copy the code