What is Block?
Block
is a special vnode
, which is used to store an extra dynamicChildren
99096be15cf8bc68fc997be15cf8bc68fc9 compared to the normal vnode
dynamic node.
What is a dynamic node?观察vnode
, children
中的第vnode
的children
是动态的,第二个vnode
class
is dynamic, and the two vnode
are dynamic nodes. Dynamic nodes will have a patchFlag
attribute, which is used to indicate which attributes of the node are dynamic.
const vnode = {
type: 'div',
children: [
{ type: 'span', children: ctx.foo, patchFlag: PatchFlags.TEXT },
{ type: 'span', children: 'foo', props: { class: normalizeClass(cls) }, patchFlag: PatchFlags.CLASS },
{ type: 'span', children: 'foo' }
]
}
As Block
, all its descendant dynamic nodes will be collected into dynamicChildren
(child dynamic elements of descendants will also be collected into dynamicChildren
).
const vnode = {
type: 'div',
children: [
{ type: 'span', children: ctx.foo, patchFlag: PatchFlags.TEXT },
{ type: 'span', children: 'foo', props: { class: normalizeClass(cls) }, patchFlag: PatchFlags.CLASS },
{ type: 'span', children: 'foo' }
],
dynamicChildren: [
{ type: 'span', children: ctx.foo, patchFlag: PatchFlags.TEXT },
{ type: 'span', children: 'foo', props: { class: normalizeClass(cls) }, patchFlag: PatchFlags.CLASS }
]
}
Which nodes will serve as Blocks?
The root node in the template, the node with v-for
, v-if/v-else-if/v-else
will be treated as Block
. The following example:
Collection of dynamicChildren
Observe the compiled code of tempalte
, you will find that before creating Block
a openBlock
function will be executed.
// 一个block栈用于存储
export const blockStack: (VNode[] | null)[] = []
// 一个数组,用于存储动态节点,最终会赋给dynamicChildren
export let currentBlock: VNode[] | null = null
export function openBlock(disableTracking = false) {
blockStack.push((currentBlock = disableTracking ? null : []))
}
openBlock
中, disableTracking
ee30ae6851895f48c8daca289dc2ea75---为true
, currentBlock
null
;否则创建一个新的Array and assign it to currentBlock
, and push
to blockStack
.
Look again createBlock
, createBlock
call a setupBlock
method.
export function createBlock(
type: VNodeTypes | ClassComponent,
props?: Record<string, any> | null,
children?: any,
patchFlag?: number,
dynamicProps?: string[]
): VNode {
return setupBlock(
createVNode(
type,
props,
children,
patchFlag,
dynamicProps,
true /* isBlock: prevent a block from tracking itself */
)
)
}
setupBlock
Receive a vnode
parameter.
function setupBlock(vnode: VNode) {
// isBlockTreeEnabled > 0时,将currentBlock赋值给vnode.dynamicChildren
// 否则置为null
vnode.dynamicChildren =
isBlockTreeEnabled > 0 ? currentBlock || (EMPTY_ARR as any) : null
// 关闭block
closeBlock()
// 父block收集子block
// 如果isBlockTreeEnabled > 0,并且currentBlock不为null,将vnode放入currentBlock中
if (isBlockTreeEnabled > 0 && currentBlock) {
currentBlock.push(vnode)
}
// 返回vnode
return vnode
}
closeBlock
:
export function closeBlock() {
// 弹出栈顶block
blockStack.pop()
// 将currentBlock设置为父block
currentBlock = blockStack[blockStack.length - 1] || null
}
Before understanding the collection process of dynamicChildren
, we should first understand that the creation order of nesting vnode
is performed from the inside out. like:
export default defineComponent({
render() {
return createVNode('div', null, [
createVNode('ul', null, [
createVNode('li', null, [
createVNode('span', null, 'foo')
])
])
])
}
})
vnode
为: span
-> li
-> ul
-> div
。
Before each creation of Block
, you need to call openBlock
to create a new array and assign it to currentBlock
, and put it on the stack blockStack
createBlock
, createBlock
中会先vnode
, vnode
参数传递给setupBlock
.
When creating vnode
, if certain conditions are met, vnode
will be collected into currentBlock
.
// 收集当前动态节点到currentBlock中
if (
isBlockTreeEnabled > 0 &&
// 避免收集自己
!isBlockNode &&
// 存在parent block
currentBlock &&
// vnode.patchFlag需要大于0或shapeFlag中存在ShapeFlags.COMPONENT
// patchFlag的存在表明该节点需要修补更新。
// 组件节点也应该总是打补丁,因为即使组件不需要更新,它也需要将实例持久化到下一个 vnode,以便以后可以正确卸载它
(vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
vnode.patchFlag !== PatchFlags.HYDRATE_EVENTS
) {
currentBlock.push(vnode)
}
setupBlock
中, currentBlock
vnode.dynamicChildren
, closeBlock
关闭block
(弹出blockStack
栈顶元素, currentBlock
blockStack
, block
的父block
), and then collect vnode
into the parent block
.
Example
In order to clear the collection process of dynamicChildren
, we continue the analysis with an example.
<template>
<div>
<span v-for="item in data">{{ item }}</span>
<ComA :count="count"></ComA>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const data = reactive([1, 2, 3])
const count = ref(0)
</script>
The above example, the code generated by the compiler after compilation is as follows. SFC Playground
import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, resolveComponent as _resolveComponent, createVNode as _createVNode } from "vue"
import { ref, reactive } from 'vue'
const __sfc__ = {
__name: 'App',
setup(__props) {
const data = reactive([1, 2, 3])
const count = ref(0)
return (_ctx, _cache) => {
const _component_ComA = _resolveComponent("ComA")
return (_openBlock(), _createElementBlock("div", null, [
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(data, (item) => {
return (_openBlock(), _createElementBlock("span", null, _toDisplayString(item), 1 /* TEXT */))
}), 256 /* UNKEYED_FRAGMENT */)),
_createVNode(_component_ComA, { count: count.value }, null, 8 /* PROPS */, ["count"])
]))
}
}
}
__sfc__.__file = "App.vue"
export default __sfc__
When the rendering function (the rendering function here is the return value of setup
) is executed, the execution flow is as follows:
- Execute
_openBlock()
to create a new array (call itdiv-block
), andpush
to the top of the stackblockStack
- Execute
_openBlock(true)
, because the parameter istrue
, so it will not create a new array, but assignnull
push
currentBlock
push
toblockStack
top of stack -
_renderList
,_renderList
会遍历---b346fda5c0882b48c54cf1836307fc15data
,并执行第二个renderItem
参数,(item) => { ... }
。 -
item
1
,执行---cfbbf48c92d4c1dde28a3f48998f8683renderItem
,执行---87b9d1704ba6245abb7233378ff1c0e3_openBlock()
(span1-block
), andpush
toblockStack
top of the stack. At this timeblockStack
,currentBlock
status is as follows: -
_createElementBlock("span", null, _toDisplayString(item), 1 /* TEXT */)
,_createElementBlock
中会先createBaseVNode
3d4223f1624ea3dbe5d8016af972a80e---创建vnode
,在创建---44a04096e3a04626a1b220e67afb4635vnode
It is ablock vnode
(isBlockNode
parameter istrue
), so it will not be collected incurrentBlock
- After creating
vnode
, executesetupBlock
and assigncurrentBlock
tovnode.dynamicChildren
. - Execute
closeBlock()
, pop the top element ofblcokStack
currentBlock
, and pointblcokStack
to the last element in ---d4b16b5b400013fcbd0a---. As shown below: - Since
currentBlock
isnull
at this time, skipcurrentBlock.push(vnode)
. -
item = 2、item = 3
, the process is the same as4-7
. Whenitem = 3
, the status afterblock
is created is as follows: - At this point,
list
rendered, and then_createElementBlock(_Fragment)
is called. -
_createElementBlock
的过程中,isBlockNode
参数true
currentBlock
null
,所以不会被currentBlock
collection -
setupBlock
,EMPTY_ARR
(空数组)vnode.dynamicChildren
,并调用closeBlock()
元素,使currentBlcok
points to the latest top stack element. SincecurrentBlock
is notnull
at this time, so executecurrentBlock.push(vnode)
-
_createVNode(_component_ComA)
,vnode
中,vnode.patchFlag === PatchFlag.PROPS
,vnode
到currentBlock
中。 - Execute
_createElementBlock('div')
. Createvnode
first, becauseisBlockNode
istrue
, so it will not be collected incurrentBlock
. - Execute
setupBlock()
and assigncurrentBlock
tovnode.dynamicChildren
. Then executecloseBlock()
to pop the top element of the stack. At this time, the length ofblockStack
is 0, socurrentBlock
will point tonull
The final generated vnode
:
{
type: "div",
children:
[
{
type: Fragment,
children: [{
type: "span",
children: "1",
patchFlag: PatchFlag.TEXT,
dynamicChildren: [],
},
{
type: "span",
children: "2",
patchFlag: PatchFlag.TEXT,
dynamicChildren: [],
},
{
type: "span",
children: "3",
patchFlag: PatchFlag.TEXT,
dynamicChildren: [],
}],
patchFlag: PatchFlag.UNKEYED_FRAGMENT,
dynamicChildren: []
},
{
type: ComA,
children: null,
patchFlag: PatchFlag.PROPS,
dynamicChildren: null
}
]
,
patchFlag:0,
dynamicChildren: [
{
type: Fragment,
children: [{
type: "span",
children: "1",
patchFlag: PatchFlag.TEXT,
dynamicChildren: [],
},
{
type: "span",
children: "2",
patchFlag: PatchFlag.TEXT,
dynamicChildren: [],
},
{
type: "span",
children: "3",
patchFlag: PatchFlag.TEXT,
dynamicChildren: [],
}],
patchFlag: PatchFlag.UNKEYED_FRAGMENT,
dynamicChildren: []
},
{
type: ComA,
children: null,
patchFlag: PatchFlag.PROPS,
dynamicChildren: null
}
]
}
The role of Block
If you understand the Diff process, you should know that during the Diff
process, a comparison is made even if the vnode
has not changed. Block
的比较, Block
中的动态节点都会被收集到dynamicChildren
中, Block
between patch
can directly compare the nodes in dynamicChildren
, reducing the comparison between non-dynamic nodes.
Block
patch
时,会patchBlockChildren
方法dynamicChildren
patch
。
const patchElement = (
n1: VNode,
n2: VNode,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
// ...
let { patchFlag, dynamicChildren, dirs } = n2
if (dynamicChildren) {
patchBlockChildren(
n1.dynamicChildren!,
dynamicChildren,
el,
parentComponent,
parentSuspense,
areChildrenSVG,
slotScopeIds
)
if (__DEV__ && parentComponent && parentComponent.type.__hmrId) {
traverseStaticChildren(n1, n2)
}
} else if (!optimized) {
patchChildren(
n1,
n2,
el,
null,
parentComponent,
parentSuspense,
areChildrenSVG,
slotScopeIds,
false
)
}
// ...
}
patchElement
中如果新节点dynamicChildren
,说明此时新节点是个Block
,那么会patchBlockChildren
dynamicChildren
patch
; optimized
420f92b546b3d40af423903cb345f7f4---为---4ac7d862012798afcd71a85213957dc7 false
patchChildren
, patchChildren
patchKeyedChildren/patchUnkeyedChildren
do Diff
.
const patchBlockChildren: PatchBlockChildrenFn = (
oldChildren,
newChildren,
fallbackContainer,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds
) => {
for (let i = 0; i < newChildren.length; i++) {
const oldVNode = oldChildren[i]
const newVNode = newChildren[i]
// 确定父容器
const container =
oldVNode.el &&
(oldVNode.type === Fragment ||
!isSameVNodeType(oldVNode, newVNode) ||
oldVNode.shapeFlag & (ShapeFlags.COMPONENT | ShapeFlags.TELEPORT))
? hostParentNode(oldVNode.el)!
: fallbackContainer
patch(
oldVNode,
newVNode,
container,
null,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
true
)
}
}
Summarize
Block
is a means of performance optimization in vue3
. Block
vnode
,它与普通vnode
,多出了一个dynamicChildren
中Saves all dynamic nodes of Block
children. Block
to perform patch
can directly compare dynamicChildren
with dynamic nodes in patch
comparison between static nodes.
The creation process of Block
:
- Before creating a
Block
node, you need to call theopenBlcok
method, create a new array and assign it toblockStack
currentBlock
, andpush
The top of the stack forblockStack
. - In the process of creating
vnode
, if some conditions are met, the dynamic node will be placed incurrentBlock
. - After the node is created, it is passed in
setupBlock
as a parameter.setupBlock
中,currentBlock
复制给vnode.dynamicChildren
,并调用closeBlcok
,弹出---f10c431a5157c2b40a8dcb08556f5e96blockStack
元素, And makecurrentBlock
point to the latest top stack element. Finally, ifcurrentBlock
is not empty at this time, collect ---822cc7ada8990ac9a63d6b04c0a490f5vnode
intocurrentBlock
.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。