csdn 迁移中
在 vue3.0 中,让人津津乐道的功能之一,就是静态提升功能了,这里就来简单学习一下这方面的源码
1、表现
首先来一个表面结论:那就是 带有for、if 本身节点不会被静态提升 静态提升的不仅仅是节点,还有prop属性
2、源码分析
一切的入口
function hoistStatic(root, context) {
walk(root.children, context, new Map(),
// Root node is unfortuantely non-hoistable due to potential parent
// fallthrough attributes.
isSingleElementRoot(root, root.children[0]));
}
function walk(children, context, resultCache, doNotHoistNode = false) {
for (let i = 0; i < children.length; i++) {
const child = children[i];
// only plain elements are eligible for hoisting.
// 只有普通元素和文本节点才能被静态提升
if (child.type === 1 /* ELEMENT */ &&
child.tagType === 0 /* ELEMENT */) {
if (!doNotHoistNode &&
// 是否是静态节点,这里会有一个递归查找的过程,
// 并对当前节点的 静态状态进行缓存
isStaticNode(child, resultCache)) {
// 设置 patchFlag,表示已经被 静态提升了
child.codegenNode.patchFlag =
-1 /* HOISTED */ + ( ` /* HOISTED */` );
const hoisted = context.transformHoist
? context.transformHoist(child, context)
: child.codegenNode;
child.codegenNode = context.hoist(hoisted);
continue;
}
else {
// node may contain dynamic children, but its props may be eligible for
// hoisting.
// 节点可能包含一些动态的子节点,但是他的 属性也是可以被 静态提升的
const codegenNode = child.codegenNode;
if (codegenNode.type === 13 /* VNODE_CALL */) {
// 当前节点是否被处理过
const flag = getPatchFlag(codegenNode);
if ((!flag ||
flag === 512 /* NEED_PATCH */ ||
flag === 1 /* TEXT */) &&
// 子节点不包含 key 或者 ref
!hasDynamicKeyOrRef(child) &&
// 默认为 false
!hasCachedProps()) {
const props = getNodeProps(child);
if (props) {
codegenNode.props = context.hoist(props);
}
}
}
}
}
// 递归遍历子节点
if (child.type === 1 /* ELEMENT */) {
walk(child.children, context, resultCache);
}
// v-for 递归遍历子节点
else if (child.type === 11 /* FOR */) {
// Do not hoist v-for single child because it has to be a block
walk(child.children, context, resultCache, child.children.length === 1);
}
// v-if 递归遍历子节点
else if (child.type === 9 /* IF */) {
for (let i = 0; i < child.branches.length; i++) {
const branchChildren = child.branches[i].children;
// Do not hoist v-if single child because it has to be a block
walk(branchChildren, context, resultCache, branchChildren.length === 1);
}
}
// 文本节点
else if (child.type === 12 /* TEXT_CALL */ &&
isStaticNode(child.content, resultCache)) {
child.codegenNode = context.hoist(child.codegenNode);
}
}
}
- 可以看到,这里对节点的子节点进行了一个递归遍历的过程
- 如果节点包含动态节点的话,不对当前节点进行静态提升,但是可以对当前节点的 prop 进行静态提升
- 如果当前节点是 v-for 节点的话,对 被遍历的节点进行 递归遍历,判断是否需要被静态提升
- 如果 v-if 遇到了类似于
<div v-if="has" class="if">aaa</div>这样的属性的话,同样不会对里面的aaa进行提升 如果是文本节点的话,判断是否是静态节点(排除类似于<div>hello {{world}}</div>) ,然后进行提升
判断是否是 静态节点 isStaticNode
function isStaticNode(node, resultCache = new Map()) {
switch (node.type) {
case 1 /* ELEMENT */:
if (node.tagType !== 0 /* ELEMENT */) {
return false;
}
// 有缓存的直接读缓存
const cached = resultCache.get(node);
if (cached !== undefined) {
return cached;
}
const codegenNode = node.codegenNode;
// 不是一个 vnode
if (codegenNode.type !== 13 /* VNODE_CALL */) {
return false;
}
// 获取当前节点的 patchFlag ,是否已经被当作静态节点处理过了
const flag = getPatchFlag(codegenNode);
// 是否已经被当作静态节点处理过了
// 当前节点是否有 key 或者 ref
// hasCachedProps 直接返回 false
if (!flag && !hasDynamicKeyOrRef(node) && !hasCachedProps()) {
// element self is static. check its children.
for (let i = 0; i < node.children.length; i++) {
// 递归查询当前子节点是否是 静态节点
// 只要有一个子节点不是静态的,当前节点就不是静态的
if (!isStaticNode(node.children[i], resultCache)) {
resultCache.set(node, false);
return false;
}
}
// 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) {
codegenNode.isBlock = false;
}
resultCache.set(node, true);
return true;
}
else {
// 设置缓存
resultCache.set(node, false);
return false;
}
case 2 /* TEXT */:
case 3 /* COMMENT */:
return true;
case 9 /* IF */:
case 11 /* FOR */:
case 10 /* IF_BRANCH */:
return false;
case 5 /* INTERPOLATION */:
case 12 /* TEXT_CALL */:
return isStaticNode(node.content, resultCache);
case 4 /* SIMPLE_EXPRESSION */:
// 简单表达式,查看这个节点是否是不变的
return node.isConstant;
case 8 /* COMPOUND_EXPRESSION */:
// 复杂表达式,
return node.children.every(child => {
return (isString(child) || isSymbol(child) || isStaticNode(child, resultCache));
});
default:
return false;
}
}
- 这里也对子节点进行了一个递归调用
- 只要有一个子节点不是静态的,当前节点就不是静态的 (会走 修改 prop 的路)
- 如果节点是 纯文本节点、注释节点,那么就是静态的
- 如果节点 带有 v-if v-for 指令 就不是静态的
- 如果是简单表达式,查看这个节点是否是不变的
- 如果是复杂表达式,看看每个节点是否都是 静态的
context.hoist 转为一个简单表达式节点
hoist(exp) {
context.hoists.push(exp);
return createSimpleExpression(`_hoisted_${context.hoists.length}`, false, exp.loc, true);
}
// ======================
function createSimpleExpression(content, isStatic, loc = locStub, isConstant = false) {
return {
type: 4 /* SIMPLE_EXPRESSION */,
loc,
isConstant,
content,
isStatic
};
}