Vue3.0 源码学习之静态提升

248 阅读2分钟

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);
          }
      }
  }
  1. 可以看到,这里对节点的子节点进行了一个递归遍历的过程
  2. 如果节点包含动态节点的话,不对当前节点进行静态提升,但是可以对当前节点的 prop 进行静态提升
  3. 如果当前节点是 v-for 节点的话,对 被遍历的节点进行 递归遍历,判断是否需要被静态提升
  4. 如果 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;
      }
  }
  1. 这里也对子节点进行了一个递归调用
  2. 只要有一个子节点不是静态的,当前节点就不是静态的 (会走 修改 prop 的路)
  3. 如果节点是 纯文本节点、注释节点,那么就是静态的
  4. 如果节点 带有 v-if v-for 指令 就不是静态的
  5. 如果是简单表达式,查看这个节点是否是不变的
  6. 如果是复杂表达式,看看每个节点是否都是 静态的

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
      };
  }