The following parsing is based on the [email protected] version
Vue3.0 series – Responsive
There is an overall mind map at the end
preface
Renderers are the most important part of Vue and are also very complex, including element rendering, Component rendering, text rendering, etc. Vue3 also references Teleport and Suspense. This article focuses on how the following code is eventually rendered into the browser.
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp({
template: '<div>demo</div><App />'.components: {App}
})
Copy the code
Before introducing rendering logic, vue3.0 has already extracted dom operations, including prop handling (class, style, event, attR), into the Runtime-DOM module, while the core rendering logic is in the Runtime-core module. This makes vue3.0 easy to render on platforms other than browsers, such as applets, RN, etc.
The whole process
Here is a brief introduction to the previous process, detailed can be combined with the final mind map to view the source
- Execute createApp to create an app instance containing global methods such as use, Component, and provide
- Run app.mount to create a vnode for template
- Perform patch
patch
Patch is the entry point for all rendering. Check out the code
n1
: old vnode, render null for the first time, compare N1 and n2 for updaten2
: The new Vnode to rendercontainer
: render containeranchor
The adjacent rendered node, n2, is rendered above the anchor for positioning
const patch: PatchFn = (n1,n2,container,anchor = null,parentComponent = null,parentSuspense = null,isSVG = false,optimized = false
) = > {
// if n1 exists and is not the same node, unload N1 and render n2 directly
if(n1 && ! isSameVNodeType(n1, n2)) { anchor = getNextHostNode(n1) unmount(n1, parentComponent, parentSuspense,true)
n1 = null
}
if (n2.patchFlag === PatchFlags.BAIL) {
optimized = false
n2.dynamicChildren = null
}
const { type, ref, shapeFlag } = n2
switch (type) {
case Text:
processText(n1, n2, container, anchor)
break
case Comment:
processCommentNode(n1, n2, container, anchor)
break
case Static:
if (n1 == null) {
mountStaticNode(n2, container, anchor, isSVG)
} else if (__DEV__) {
patchStaticNode(n1, n2, container, isSVG)
}
break
case Fragment:
processFragment(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else if (shapeFlag & ShapeFlags.COMPONENT) {
processComponent(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else if(shapeFlag & ShapeFlags.TELEPORT) { ; (typeas typeof TeleportImpl).process(
n1 as TeleportVNode,
n2 as TeleportVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized,
internals
)
} else if(__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { ; (typeas typeof SuspenseImpl).process(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized,
internals
)
}
}
}
Copy the code
The main thing here is to call different render functions based on different types. Can be divided into:
processText
: Processing textprocessCommentNode
: Handling commentsmountStaticNode
andpatchStaticNode
, this is mainly to render static HTML generated by compile, used for SSR renderingprocessFragment
: deal with fragmentsprocessElement
: Processing nodeprocessComponent
: Processing componentstype.process
Suspense: Handle teleport and suspense
This section describes the implementation of processFragment, processElement, processComponent, and Teleport
Start by listing what the compiled code looks like
processFragment
DynamicChildren is a new feature of vue3.0. During compilation, dynamicChildren will be put into the dynamicChildren attribute. During patch, only the nodes in dynamicChildren need to be patched, not the static nodes. For example, _hoisted_1 above skips patch, which can be implemented in the next article
const processFragment = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) = > {
// Fragment start node, empty text
const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(' '))!
// End node of fragment, empty text. Fragment children will be rendered in this area
const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(' '))!
// patchFlag is highlighted in red in the figure above. If it is smaller than patchFlag, it means that it is a static node, and patch is not required. DynamicChildren is a dynamic node
let { patchFlag, dynamicChildren } = n2
if (patchFlag > 0) {
optimized = true
}
if (n1 == null) {
// n1 == null, then render node
hostInsert(fragmentStartAnchor, container, anchor)
hostInsert(fragmentEndAnchor, container, anchor)
// Perform patch on each child
mountChildren(
n2.children as VNodeArrayChildren,
container,
fragmentEndAnchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else {
// If a patch is required with a dynamic node, only a patch dynamic node is required, which is an optimization of VUe3
if (
patchFlag > 0 &&
patchFlag & PatchFlags.STABLE_FRAGMENT &&
dynamicChildren
) {
// Make patch for each dynamic nodepatchBlockChildren( n1.dynamicChildren! , dynamicChildren, container, parentComponent, parentSuspense, isSVG )if( n2.key ! =null ||
(parentComponent && n2 === parentComponent.subTree)
) {
// This is mainly because only patch dynamic node, n2 static node does not have EL, and the subsequent mount will report an error, so the el of N1 is assigned to n2.el
traverseStaticChildren(n1, n2, true /* shallow */)}}else {
// The fragment generated by V-for does not have dynamicChildren, because Vue considers that every node is a block and each node needs to do patch
patchChildren(
n1,
n2,
container,
fragmentEndAnchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
}
}
Copy the code
The above process mainly creates the empty text node, and then renders children directly in the empty text node. Next, we will focus on the implementation of patchChildren
patchChildren
const patchChildren: PatchChildrenFn = (
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized = false
) = > {
const c1 = n1 && n1.children
const prevShapeFlag = n1 ? n1.shapeFlag : 0
const c2 = n2.children
const { patchFlag, shapeFlag } = n2
// If patchFlag is greater than 0, patchFlag needs to be made
if (patchFlag > 0) {
if (patchFlag & PatchFlags.KEYED_FRAGMENT) {
// If the key is set, the diff algorithm is performed
patchKeyedChildren(
c1 as VNode[],
c2 as VNodeArrayChildren,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
return
} else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
If it is the same node, do patch. If it is not, uninstall the old node and render the new node.
patchUnkeyedChildren(
c1 as VNode[],
c2 as VNodeArrayChildren,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
return}}// I don't understand why the following comparison, temporarily did not think of the scene
// children has 3 possibilities: text, array or no children.
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
// text children fast path
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
unmountChildren(c1 as VNode[], parentComponent, parentSuspense)
}
if(c2 ! == c1) { hostSetElementText(container, c2as string)
}
} else {
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
// prev children was array
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
// two arrays, cannot assume anything, do full diff
patchKeyedChildren(
c1 as VNode[],
c2 as VNodeArrayChildren,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else {
// no new children, just unmount old
unmountChildren(c1 as VNode[], parentComponent, parentSuspense, true)}}else {
// prev children was text OR null
// new children is array OR null
if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
hostSetElementText(container, ' ')}// mount new if array
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(
c2 as VNodeArrayChildren,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
}
}
}
Copy the code
Todo: I don’t understand why patchFlag>0 is used and why I continue to make judgment. I haven’t thought of the scene for the time being. There are big guys think of the corresponding scene under guidance ~
The diff algorithm
Diff algorithm will be carried out in patchKeyedChildren, which is also different from VUe2, because there are too many codes, so the code is subsidized, and the process is expressed in words
vue2.x
Vue2 uses two-pointer traversal
- Whether the first node of the old children and the first node of the new children are the same, then patch
- If the old tail node and the new tail node are the same, patch
- If the old tail node is the same as the new first node, patch is displayed
- If the old first node is the same as the new last node, patch is displayed
- Whether the key of the new node is in the old children. If yes, is it the same node? If yes, patch; otherwise, create a new node. If not, create a new node
- Whether the traversal of the old node or the new node is complete, if not, continue the above process
- After the traversal is complete, the unprocessed nodes in the new Choldren are inserted and the redundant nodes in the old children are deleted
vue3.x
Vue3 uses the algorithm of single pointer traversal + longest increasing subsequence
- If n nodes in the head are the same, the patch nodes are the same
- N nodes at the tail are the same, and nodes at the tail of patch are the same
- If no differential node exists and the new node is redundant, mount the new node
- If there is no differential node and the old node is redundant, unmount the redundant old node
- There are difference nodes in the middle
- Create the map object for key: I (key value and subscript) of the new difference node
- The old difference node is traversed from left to right. If no match is found in the new difference node, it is unloaded; otherwise, patch is made to the matched node. At the same time, the new difference node is generated into the corresponding old difference node index array
- The matching nodes of the new difference node and the old difference node are in order, so the new node is directly rendered from right to left without moving the node (the new node can be confirmed according to whether the object of the old and new node index generated in the previous step can match the old node).
- If the order does not match, the longest increasing subsequence is obtained from the index mapping array of the old and new nodes, and the nodes on the increasing subsequence do not need to be moved
- The number of new differential nodes is traversed from right to left. If the index is not in the increasing subsequence, it means that the corresponding old node needs to be moved to this position
Here’s an example:
n1: n k a b c d e f g m
n2: n k e b a d f c g m
- patch
n
和k
- patch
g
和m
- Create a map object: keyToNewIndexMap = {e: 2, b: 3, a: 4, D: 5, f: 6, c: 7}
- right
a
b
c
d
e
f
Make patch(the node position is still the original, but the node properties are updated). Create array: newIndexToOldIndexMap = [7, 4, 3, 6, 8, 5] - If newIndexToOldIndexMap is not an increasing sequence, the longest increasing sub-sequence can be calculated: [4, 6, 8], i.e
b
d
f
The positions of the three nodes do not need to change - N2 then moves the node from right to left.
- Start by changing the position of C from 5(
b
After) move tog
In front of; - because
f
In an increasing subsequence, sof
You don’t need to move; - because
d
In an increasing subsequence, sod
You don’t need to move; - will
a
Moved tod
In the front of the - because
b
In an increasing subsequence, sob
You don’t need to move; - The final will be
e
Moved tob
Just in front of
- Start by changing the position of C from 5(
processElement
MountElement and patchElement are invoked in processElement. The former is the rendering process and the latter is the update process
mountElement
This piece of code is relatively simple and mainly lists its steps
- Create the corresponding EL
- Set the content of el, if the content is text, create a file to insert EL, if the content is array, patch each child, again follow the judgment logic
- Created hooks that trigger the directive
- Handles props for el, including class, style, event, etc
- Set el’s scopedId
- Triggers the beforeMount hook of directive
- To perform the transition. BeforeEnter (el)
- Insert el into the page
- To perform the transition. Enter (el)
- Mounted hook that triggers the directive
patchElement
- Triggers the beforeUpdate hook of directive
- patchProp
- There is an optimization because dynamic prop is marked at compile time. Specific patches can be removed according to class, style, prop, etc. If it is full_props (with dynamic keys), the following patches need to be made
- If there are dynamic nodes, then
patchBlockChildren
Or go if you need topatchChildren
processComponent
There are also mountComponent and updateComponent. Too much content, subsidize the source code
mountComponent
- CreateComponentInstance, which creates a component instance. Emit = emit. Bind (null, instance)
- SetupComponent (instance), installs the component
- initProps(instance, props, isStateful, isSSR)
- Handle prop, assign to instance.prop and instance.attrs, and undeclared attributes and emit events will be placed in attrs
-
This is going to be responsive to props. In production mode, this props is passed to the first parameter of setup (dev mode wraps another layer with readOnly), so modifying props directly in setup also triggers an update of the current component, but not the parent component, and the value of the parent component does not change
- InitSlots (instance, children), which binds the slot contents to instance instances
- Create a proxy for CTX (_: instance) that contains props, setupResult, data, $, and so on.
- Execute the setup function to get the result. If the result is a function, assign instance.render = setupResult as the render function. If it is an object, instance.setupState = proxyRefs(setupResult). Proxy result, do unref processing
- initProps(instance, props, isStateful, isSSR)
- finishComponentSetup
- If the render function is not already available, compile is executed to generate the render function
- ApplyOptions, which deals with options compatibility, is rewritten with the new API
- setupRenderEffect(
instance.update = effect(function componentEffect() {})
), effect principle for referenceResponsive article- Wrap the render function with effect if the component is not rendered. Go render logic; If the component is rendered, go to the update logic
teleport
- Gets the node, target, marked by to
- Insert the empty text node, targetAnchor, into the container, along with the empty text node mainAnchor where the Teleport was
- If the teleport is not disabled, render the children in the Teleport before the targetAnchor in target
- Otherwise, render the content in its original position