Appearance
运行时的优化
openBlock
- vue3 在运行时设计了一个 blockStack 和 currentBlock
- blockStack 表示一个 block tree 因为要考虑嵌套的情况
- currentBlock 表示当前的 block
- openBlock 就是往当前的 blockstack 添加一个新的 block 作为 currentBlock
- 主要是用来收集 动态 vnode 节点 才能在 patch 阶段只识别动态节点 避免了不必要的静态节点对比提升性能
- 动态 vnode 节点是在执行 createBaseVNode 函数创建 vnode 对象的时候搜集的
ts
export function openBlock(disableTracking = false) {
blockStack.push((currentBlock = disableTracking ? null : []))
}
createBaseVNode
- 会通过 patchFlag 判断 vnode 是不是一个动态节点 如果是 并且 isBlockTreeEnabled 大于 0 就把它添加到 currentBlock 中
- 这就是动态收集 vnode 到过程
- 还会判断 isBlockNode 到值 如果是 true 不会添加到 currentBlock
ts
function createBaseVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag = 0,
dynamicProps: string[] | null = null,
shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
isBlockNode = false,
needFullChildrenNormalization = false
) {
const vnode = {
__v_isVNode: true,
__v_skip: true,
type,
props,
key: props && normalizeKey(props),
ref: props && normalizeRef(props),
scopeId: currentScopeId,
slotScopeIds: null,
children,
component: null,
suspense: null,
ssContent: null,
ssFallback: null,
dirs: null,
transition: null,
el: null,
anchor: null,
target: null,
targetAnchor: null,
staticCount: 0,
shapeFlag,
patchFlag,
dynamicProps,
dynamicChildren: null,
appContext: null
} as VNode
if (needFullChildrenNormalization) {
normalizeChildren(vnode, children)
// normalize suspense children
if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
;(type as typeof SuspenseImpl).normalize(vnode)
}
} else if (children) {
// compiled element vnode - if children is passed, only possible types are
// string or Array.
vnode.shapeFlag |= isString(children)
? ShapeFlags.TEXT_CHILDREN
: ShapeFlags.ARRAY_CHILDREN
}
// validate key
if (__DEV__ && vnode.key !== vnode.key) {
warn(`VNode created with invalid key (NaN). VNode type:`, vnode.type)
}
// track vnode for block tree
if (
isBlockTreeEnabled > 0 &&
// avoid a block node from tracking itself
!isBlockNode &&
// has current parent block
currentBlock &&
// presence of a patch flag indicates this node needs patching on updates.
// component nodes also should always be patched, because even if the
// component doesn't need to update, it needs to persist the instance on to
// the next vnode so that it can be properly unmounted later.
(vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
// the EVENTS flag is only for hydration and if it is the only flag, the
// vnode should not be considered dynamic due to handler caching.
vnode.patchFlag !== PatchFlags.HYDRATE_EVENTS
) {
currentBlock.push(vnode)
}
if (__COMPAT__) {
convertLegacyVModelProps(vnode)
defineLegacyVNodeProperties(vnode)
}
return vnode
}
createElementBlock
- 本质就是通过 setupBlock 函数封装 createBaseVNode 函数生成的 vnode 将其变成一个 block vnode
- 首先执行 createBaseVNode 创建一个 vnode 节点 最后一个参数 true 表示是一个 block vnode
- 不会把自身当作一个动态 vnode 收集到 currentBlock
- 如何执行 setupBlock 收集动态子节点 的 currentBlock 保留到当前的 block vnode 的 dynamicChildren 中方便后续 patch 过程访问动态子节点
- 最后把当前 block 恢复到 父 block 如果 父 block 存在 那么把当前 block vnode 作为动态节点去添加到父 block 中
- 这样构造了 block tree
- block 的创建是执行了 createBlock 函数完成的
ts
export function createElementBlock(
type: string | typeof Fragment,
props?: Record<string, any> | null,
children?: any,
patchFlag?: number,
dynamicProps?: string[],
shapeFlag?: number
) {
return setupBlock(
createBaseVNode(
type,
props,
children,
patchFlag,
dynamicProps,
shapeFlag,
true /*isBlock*/
)
)
}
patchBlockChildren
- 主要是用来遍历新的动态子节点数组 拿到对应的新旧动态子节点 执行 patch 更新子节点
- 更新子节点会遇到 是动态 vnode 还是 block vnode
- 如果是 动态 vnode 那么它的 dynaimcChildren 为 null 由于 optimize 是 true 无需做其他
- 如果是 block vnode 会拥有 dynaimcChildren 需要递归执行 patchBlockChildren
- 通过递归可以完成组件下所有动态节点的更新
ts
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]
// Determine the container (parent element) for the patch.
const container =
// oldVNode may be an errored async setup() component inside Suspense
// which will not have a mounted element
oldVNode.el &&
// - In the case of a Fragment, we need to provide the actual parent
// of the Fragment itself so it can move its children.
(oldVNode.type === Fragment ||
// - In the case of different nodes, there is going to be a replacement
// which also requires the correct parent container
!isSameVNodeType(oldVNode, newVNode) ||
// - In the case of a component, it could contain anything.
oldVNode.shapeFlag & (ShapeFlags.COMPONENT | ShapeFlags.TELEPORT))
? hostParentNode(oldVNode.el)!
: // In other cases, the parent container is not actually used so we
// just pass the block element here to avoid a DOM parentNode call.
fallbackContainer
patch(
oldVNode,
newVNode,
container,
null,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
true
)
}
}