Welcome to follow our wechat official account: Yang Yitao
In the previous analysis of patch function, we knew that different functions were called to compare the differences between the old and new virtual nodes and to smooth out the differences through different types of judgment. At that time, part of the function implementation details of patch function call were also introduced. This paper will take you to analyze most of the source code implementation of processElement and processComponent functions, and summarize the core work flow of Patch function with a flow chart at the end of the paper. As for the specific implementation of diff function, as a difficulty, it will be explained in depth in the next article.
processElement
Let’s look at the code implementation of processElement:
// Snippet 1
const processElement = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) = > {
isSVG = isSVG || (n2.type as string) = = ='svg'
if (n1 == null) {
mountElement(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else {
patchElement(
n1,
n2,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
}
}
Copy the code
For now, we will focus only on the first four parameters of processElement.
n1
: oldVirtual Node;n2
New:Virtual Node;container
: Virtual NodeintoReal NodeTo be mounted laterDOMElements;anchor
:Virtual NodeintoReal NodeTo be mounted laterDOMWhere exactly on the element.
Now that we know about the parameters, let’s look at conditional judgments:
// Snippet 2
if (n1 == null) {
mountElement(
// Omit some code here...)}else {
patchElement(
// Omit some code here...)}Copy the code
If the old virtual Node is null, it indicates that the old virtual Node does not exist, so there is no need to perform the so-called comparison. Simply call mountElement to mount the new virtual Node (n2) to the container Node. If the old virtual Node is not null, it indicates that the old Node exists, so we need to compare the difference between the two and use the code implementation with good performance to erase the difference, and the patchElement function has such ability. Next, we start to analyze the two functions of mountElement and patchElement.
mountElement
MountElement (mountElement)
// Snippet 3
const mountElement = (
vnode: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) = > {
let el: RendererElement
let vnodeHook: VNodeHook | undefined | null
const { type, props, shapeFlag, transition, patchFlag, dirs } = vnode
if(! __DEV__ && vnode.el && hostCloneNode ! = =undefined &&
patchFlag === PatchFlags.HOISTED
) {
el = vnode.el = hostCloneNode(vnode.el)
} else {
el = vnode.el = hostCreateElement(
vnode.type as string,
isSVG,
props && props.is,
props
)
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
hostSetElementText(el, vnode.children as string)}else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(
vnode.children as VNodeArrayChildren,
el,
null,
parentComponent,
parentSuspense,
isSVG && type! = ='foreignObject',
slotScopeIds,
optimized
)
}
// Some code omitted here...
}
// Some code omitted here...
hostInsert(el, container, anchor)
// Some code omitted here...
}
Copy the code
The implementation logic inside mountElement is quite rich, but to highlight the main thread, I have omitted the code that calls back instructions to declare periodic functions, handles animations and asynchrony, and adds attributes to the created DOM elements, which will be explained in this article on a specific topic. From code snippet 3, we can assume that mountElement does the following:
- According to incomingVirtual NodeCreate the correspondingReal NodeThe element
el
; - if
el
If the child element is text, the text is set to that element, or if the child element is an arraymountChildren
The function mounts each child element, and mounting to the container is what we created hereel
Of course, if the child elements of the child elements are still arrays, the recursion will continue until there are no children; - will
el
Mount to thecontainer
Element.
The first if condition judgment for snippet 3 can be confusing:
// Snippet 4
if(! __DEV__ && vnode.el && hostCloneNode ! = =undefined &&
patchFlag === PatchFlags.HOISTED
) {
el = vnode.el = hostCloneNode(vnode.el)
}
Copy the code
As we mentioned earlier, mounting means adding the created DOM element to the target DOM node. In fact, Vue3 does static variable enhancement and optimization to a certain extent during compilation process, so there is a judgment condition here. Related content will be introduced in compilation related articles, here is a brief understanding.
patchElement
Compared with mountElement, the logic of patchElement is much more complicated, because mountElement does not have the ability to find differences. You only need to create elements according to the virtual Node and mount them to the target Node. However, patchElement needs to find out the differences between the new virtual Node and the old virtual Node, and add, delete and modify the current DOM elements under the condition of good performance.
Since patchElement has a lot of content, even the key content is not small. For the sake of this description, I’ll break it down into several aspects, presenting parts of the code as separate code blocks. It mainly involves the following aspects:
patchElement
The core logic ofpatchBlockChildren
与patchChildren
Respective dutiespatchFlag
patchElement
Core functions of
As mentioned before, the core function of patchElement is to find and smooth differences. But what is the difference between finding and smoothing out? As the name implies, the object of patchElement operation is DOM element, and a DOM element actually contains two chunks of content. The first one is various attribute states of the DOM element itself. The second block is the child of the DOM element. The core function of patchElement is to use patchChildren and patchProps to find and smooth the differences of child elements and the attributes of the current DOM element respectively. Because Vue3 is internally optimized, it is not always necessary to call patchChildren and patchProps, but patchBlockChildren or other functions to complete the relevant work.
Of course, in addition to the core functions, there are branch functions, branch functions including call instructions and virtual Node corresponding and update-related life cycle functions and some asynchronous process processing, after introducing the core process, there will be a special article on related content.
patchBlockChildren
与 patchChildren
We only need to know that patchBlockChildren is related to optimization, and relevant content will be introduced at an appropriate time in subsequent articles, while patchChildren will not be covered in this paper, because this function can be said to be the soul of the whole patchElement function and its logic is complicated. The well-known DIff algorithm will also start with patchChildren function. Please look forward to the next article about the analysis of diff algorithm.
patchFlag
// Snippet 5
if (patchFlag > 0) {
if (patchFlag & PatchFlags.FULL_PROPS) {
patchProps(
el,
n2,
oldProps,
newProps,
parentComponent,
parentSuspense,
isSVG
)
} else {
if (patchFlag & PatchFlags.CLASS) {
if(oldProps.class ! == newProps.class) { hostPatchProp(el,'class'.null, newProps.class, isSVG)
}
}
if (patchFlag & PatchFlags.STYLE) {
hostPatchProp(el, 'style', oldProps.style, newProps.style, isSVG)
}
if (patchFlag & PatchFlags.PROPS) {
const propsToUpdate = n2.dynamicProps!
for (let i = 0; i < propsToUpdate.length; i++) {
const key = propsToUpdate[i]
const prev = oldProps[key]
const next = newProps[key]
if(next ! == prev || key ==='value') {
hostPatchProp(
el,
key,
prev,
next,
isSVG,
n1.children as VNode[],
parentComponent,
parentSuspense,
unmountChildren
)
}
}
}
}
if (patchFlag & PatchFlags.TEXT) {
if(n1.children ! == n2.children) { hostSetElementText(el, n2.childrenas string)}}}else if(! optimized && dynamicChildren ==null) {
patchProps(
el,
n2,
oldProps,
newProps,
parentComponent,
parentSuspense,
isSVG
)
}
Copy the code
The content logic of code fragment 5 is quite clear, but one point needs to be mentioned, that is, the code in the form of patchFlag & PatchFlags.text, which is the same principle of ShapeFlags introduced in the last article. Let’s look at the code implementation of PatchFlags:
// Snippet 6
export const enum PatchFlags {
TEXT = 1,
CLASS = 1 << 1,
STYLE = 1 << 2,
PROPS = 1 << 3,
FULL_PROPS = 1 << 4,
HYDRATE_EVENTS = 1 << 5,
STABLE_FRAGMENT = 1 << 6,
KEYED_FRAGMENT = 1 << 7,
UNKEYED_FRAGMENT = 1 << 8,
NEED_PATCH = 1 << 9,
DYNAMIC_SLOTS = 1 << 10,
DEV_ROOT_FRAGMENT = 1 << 11,
HOISTED = -1,
BAIL = -2
}
Copy the code
From code fragment 6, it is not difficult to find that patchFlag represents the type of attribute to be operated. Meanwhile, from code fragment 5 combined with the introduction of bit operation in the previous article, it is not difficult to find that variable patchFlag can express multiple states simultaneously, such as class attribute and style attribute.
processComponent
Let’s look at the source code implementation of the processComponent function:
// Snippet 7
const processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) = > {
n2.slotScopeIds = slotScopeIds
if (n1 == null) {
if(n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) { ; (parentComponent! .ctxas KeepAliveContext).activate(
n2,
container,
anchor,
isSVG,
optimized
)
} else {
mountComponent(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
} else {
updateComponent(n1, n2, optimized)
}
}
Copy the code
This logic has a high degree of similarity to processComponent, except that it has keep-alive features for components, which are not covered in this article. Let’s look at the implementation in the mountComponent and updateComponent functions.
mountComponent
Let’s start with the mountComponent function:
// Snippet 8
const mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) = > {
// Omit some code here...
const instance: ComponentInternalInstance =
compatMountInstance ||
(initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
// Omit some code here...
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
// Omit some code here...
}
Copy the code
After omitting some code, we leave behind the most critical code, which shows two key things about the mountComponent function:
- Through the function
createComponentInstance
Create a component instance; - In the function
setupRenderEffect
Creates a function that renders the child component for the component instance and passes toReactiveEffect
Instance to enable the function to be re-executed when reactive data changes.
Let’s look at the createComponentInstance and setupRenderEffect functions.
createComponentInstance
// Snippet 9
export function createComponentInstance(
vnode: VNode,
parent: ComponentInternalInstance | null,
suspense: SuspenseBoundary | null
) {
// Omit some code here...
const instance: ComponentInternalInstance = {
// Omit some code here...
}
// Omit some code here...
return instance
}
Copy the code
We need to know that a component instance is actually an object, corresponding to the object instance in snippet 9. Of course, since it is a component instance, it means that the parameter vnode represents a virtual Node of type component, which is then passed to setupRenderEffect as a parameter. Now we enter this function for analysis.
setupRenderEffect
First look at the relevant code implementation:
// Snippet 10
const setupRenderEffect: SetupRenderEffectFn = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) = > {
const componentUpdateFn = () = > {
// Omit some code here...
}
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
() = > queueJob(instance.update),
instance.scope
))
const update = (instance.update = effect.run.bind(effect) as SchedulerJob)
update.id = instance.uid
// Omit some code here...
update()
}
Copy the code
In Snippet 10, there is a lot of simplification of the logic except for the most critical logic. SetupRenderEffect does three things:
- Define a function
componentUpdateFn
; - create
ReactiveEffect
Instance of the function to be definedcomponentUpdateFn
Passed as arguments to the constructor; - the
effect.run.bind(effect)
As a component instanceinstance
theupdate
Property value;
What happens when you do these three steps? The result is that the reactive data used in the function componentUpdateFn is re-executed when it changes. We know that the mountComponent function mounts the DOM tree of the component to the target node. The core role of the function componentUpdateFn is to convert a component instance into a real DOM tree and mount that DOM tree to the container node. For details, see below.
componentUpdateFn
// Snippet 11
const componentUpdateFn = () = > {
if(! instance.isMounted) {// Omit some code here...
if (el && hydrateNode) {
// Omit some code here...
} else {
// Omit some code here...
const subTree = (instance.subTree = renderComponentRoot(instance))
// Omit some code here...
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
// Omit some code here...
initialVNode.el = subTree.el
}
// Omit some code here...
instance.isMounted = true
// Omit some code here...
initialVNode = container = anchor = null as any
} else {
// Omit some code here...
if (next) {
next.el = vnode.el
updateComponentPreRender(instance, next, optimized)
} else {
next = vnode
}
// Omit some code here...
const nextTree = renderComponentRoot(instance)
// Omit some code here...
const prevTree = instance.subTree
instance.subTree = nextTree
// Omit some code here...
patch(
prevTree,
nextTree,
// parent may have changed if it's in a teleporthostParentNode(prevTree.el!) ! .// anchor may have changed if it's in a fragment
getNextHostNode(prevTree),
instance,
parentSuspense,
isSVG
)
// Omit some code here...
next.el = nextTree.el
// Omit some code here...}}Copy the code
The function componentUpdateFn contains more than 200 lines of code, with a lot of streamlining in snippet 11. This function is the soul of component rendering and updating. If (! Instance. isMounted) {}else{} handles both mount and update.
Mount related logic
For mount operations, the function componentUpdateFn handles the server-side rendering logic that is not discussed in this article. Normally, two things are done for the mount operation:
- call
renderComponentRoot
Function that takes the component instanceinstance
Into a childVirtual NodeTree and assign a value toinstance.subTree
, and callpatch
The function takes that subVirtual NodeThe tree is mounted to the target container node. - perform
initialVNode.el = subTree.el
, and the corresponding of the child nodeel
Node assigned to componentVirtual Nodetheel
Properties.
Note here that the relationship between component type virtual Node and subTree is assumed to have the following code:
// Snippet 12 file index.vue<template>
<App></App>
</template>
Copy the code
// Snippet 13 file app.vue<template>
<div>Hello World</div>
</template>
Copy the code
Component virtual Node represents
.
renderComponentRoot
Let’s go to renderComponentRoot and explore how to get a subTree from a component instance:
// Snippet 14
export function renderComponentRoot(
instance: ComponentInternalInstance
) :VNode {
// Omit some code here...
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
constproxyToUse = withProxy || proxy result = normalizeVNode( render! .call( proxyToUse, proxyToUse! , renderCache, props, setupState, data, ctx ) ) fallthroughAttrs = attrs }// Omit some code here...
return result
}
Copy the code
After skipping a lot of code, it’s easy to see that the core job of the function renderComponentRoot is to call the render function of the component via a proxy object. Why proxy objects? The answer was already answered in the previous article, one of the most important reasons is that access to ref values does not need to be in the.value format, and the other is to protect the content of the child component from being accessed by the parent component. As for the function of the render function we have also explained in the previous article, will not be repeated here.
Update related logic
With the mount logic analyzed above, the update logic is straightforward. It can be summarized as the following two steps:
- Get component new
subTree
And what we have right nowsubTree
; - call
patch
Function to perform the update operation.
updateComponent
Let’s look at the implementation of the updateComponent function:
// Snippet 15
const updateComponent = (n1: VNode, n2: VNode, optimized: boolean) = > {
const instance = (n2.component = n1.component)!
if (shouldUpdateComponent(n1, n2, optimized)) {
if( __FEATURE_SUSPENSE__ && instance.asyncDep && ! instance.asyncResolved ) {// Omit some code here...
} else {
instance.next = n2
invalidateJob(instance.update)
instance.update()
}
} else {
// no update needed. just copy over properties
n2.component = n1.component
n2.el = n1.el
instance.vnode = n2
}
}
Copy the code
With that in mind, we can say that the core of updateComponent is to execute instance.update().
conclusion
Combined with the previous article, we can now say that we have understood the core workflow of Vue3’s rendering mechanism. Please look at this flow chart first:
Combining this flow chart with the content of the previous paper and this paper, we can have a clear understanding of theVirtual NodeintoReal NodeThe implementation process of. Please look forward to the next article ondiff
Description of the algorithm.
Write in the last
After reading the article, you can do the following things to support you:
- if
Like, look, forward
Can let the article help more people in need; - If you are the author of wechat public account, you can find me to open
White list
.reprinted
My original articles;
Finally, please pay attention to my wechat public account: Yang Yitao, you can get my latest news.