Appearance
静态提升
hoistStatic
- 节点转换完毕之后 会判断编译配置中是否配置了 hoistStatic 如果配置了就会执行 hoistStatic 做静态提升
- 静态提升是 vue3 编译阶段的一个优化策略
- 因为静态节点不依赖动态数据 一旦创建了就不会改变 所以 只有静态节点才能被提升到外部创建
- 主要操作就是从 根节点开始 通过深度优化的方式递归遍历节点并做判断
- 如果一个节点是一个元素节点 会通过 getConstantType 获取节点的常量类型
getConstantType
- 会根据节点类型的不同 返回不同的值
- 对于文本和注释节点 返回 3 CAN_STRINGIFY
- 对于元素节点 会判断节点自身以及它子元素的类型 属性类型 再去决定它的常量类型
- 如果发现没有任何动态数据 就是可以被静态提升的
- 虽然有的节点会包含一些动态节点 但是本身的静态属性还是可以被静态提升的
- 如果节点满足可以被静态提升的条件 节点对应的 codegenNode 会通过执行 context.hoist 将其修改为一个简单的表达式节点
ts
export function getConstantType(
node: TemplateChildNode | SimpleExpressionNode,
context: TransformContext
): ConstantTypes {
const { constantCache } = context
switch (node.type) {
case NodeTypes.ELEMENT:
if (node.tagType !== ElementTypes.ELEMENT) {
return ConstantTypes.NOT_CONSTANT
}
const cached = constantCache.get(node)
if (cached !== undefined) {
return cached
}
const codegenNode = node.codegenNode!
if (codegenNode.type !== NodeTypes.VNODE_CALL) {
return ConstantTypes.NOT_CONSTANT
}
if (
codegenNode.isBlock &&
node.tag !== 'svg' &&
node.tag !== 'foreignObject'
) {
return ConstantTypes.NOT_CONSTANT
}
const flag = getPatchFlag(codegenNode)
if (!flag) {
let returnType = ConstantTypes.CAN_STRINGIFY
// Element itself has no patch flag. However we still need to check:
// 1. Even for a node with no patch flag, it is possible for it to contain
// non-hoistable expressions that refers to scope variables, e.g. compiler
// injected keys or cached event handlers. Therefore we need to always
// check the codegenNode's props to be sure.
const generatedPropsType = getGeneratedPropsConstantType(node, context)
if (generatedPropsType === ConstantTypes.NOT_CONSTANT) {
constantCache.set(node, ConstantTypes.NOT_CONSTANT)
return ConstantTypes.NOT_CONSTANT
}
if (generatedPropsType < returnType) {
returnType = generatedPropsType
}
// 2. its children.
for (let i = 0; i < node.children.length; i++) {
const childType = getConstantType(node.children[i], context)
if (childType === ConstantTypes.NOT_CONSTANT) {
constantCache.set(node, ConstantTypes.NOT_CONSTANT)
return ConstantTypes.NOT_CONSTANT
}
if (childType < returnType) {
returnType = childType
}
}
// 3. if the type is not already CAN_SKIP_PATCH which is the lowest non-0
// type, check if any of the props can cause the type to be lowered
// we can skip can_patch because it's guaranteed by the absence of a
// patchFlag.
if (returnType > ConstantTypes.CAN_SKIP_PATCH) {
for (let i = 0; i < node.props.length; i++) {
const p = node.props[i]
if (p.type === NodeTypes.DIRECTIVE && p.name === 'bind' && p.exp) {
const expType = getConstantType(p.exp, context)
if (expType === ConstantTypes.NOT_CONSTANT) {
constantCache.set(node, ConstantTypes.NOT_CONSTANT)
return ConstantTypes.NOT_CONSTANT
}
if (expType < returnType) {
returnType = expType
}
}
}
}
// only svg/foreignObject could be block here, however if they are
// static then they don't need to be blocks since there will be no
// nested updates.
if (codegenNode.isBlock) {
// except set custom directives.
for (let i = 0; i < node.props.length; i++) {
const p = node.props[i]
if (p.type === NodeTypes.DIRECTIVE) {
constantCache.set(node, ConstantTypes.NOT_CONSTANT)
return ConstantTypes.NOT_CONSTANT
}
}
context.removeHelper(OPEN_BLOCK)
context.removeHelper(
getVNodeBlockHelper(context.inSSR, codegenNode.isComponent)
)
codegenNode.isBlock = false
context.helper(getVNodeHelper(context.inSSR, codegenNode.isComponent))
}
constantCache.set(node, returnType)
return returnType
} else {
constantCache.set(node, ConstantTypes.NOT_CONSTANT)
return ConstantTypes.NOT_CONSTANT
}
case NodeTypes.TEXT:
case NodeTypes.COMMENT:
return ConstantTypes.CAN_STRINGIFY
case NodeTypes.IF:
case NodeTypes.FOR:
case NodeTypes.IF_BRANCH:
return ConstantTypes.NOT_CONSTANT
case NodeTypes.INTERPOLATION:
case NodeTypes.TEXT_CALL:
return getConstantType(node.content, context)
case NodeTypes.SIMPLE_EXPRESSION:
return node.constType
case NodeTypes.COMPOUND_EXPRESSION:
let returnType = ConstantTypes.CAN_STRINGIFY
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i]
if (isString(child) || isSymbol(child)) {
continue
}
const childType = getConstantType(child, context)
if (childType === ConstantTypes.NOT_CONSTANT) {
return ConstantTypes.NOT_CONSTANT
} else if (childType < returnType) {
returnType = childType
}
}
return returnType
default:
if (__DEV__) {
const exhaustiveCheck: never = node
exhaustiveCheck
}
return ConstantTypes.NOT_CONSTANT
}
}
静态提升的成本
- 静态提升过程最终结果还是修改了可以被静态提升的节点的 codegenNode
- 静态提升创建的节点 放在的 render 函数的外部
- render 函数内部时钟会保持对静态节点的引用 导致的后果就是 组件即使销毁 静态提升的节点所占用的内存不会释放
- 静态提升是空间换时间的优化手段 相比于被占用的内存成本 性能方面的提升会给用户带来更多的收益