Vue2的静态节点、静态根节点

1,038 阅读2分钟

为什么要标记静态根节点?

标记静态节点是为了最终得到静态根节点;

vue关于这部分的优化是当一个节点staticRoots为true,并且不在v-for中,那么第一次render的时候会对以这个节点为根的子树(vnode)进行缓存,等到下次再render的时候直接从缓存中拿,避免再次render。所以标记静态根节点是为了缓存一棵子树用的。

编译生成render函数

  1. 首先将template字符串解析成AST树
  2. 遍历AST树,标记静态节点
  3. 标记静态根节点
  4. 生成render函数
 <div id="app">
     <div> <span>静态根节点</span></div>
      //当前节点不是静态根节点,children只有一个且是一个文本,则不是根节点。
      /** 
      官方说明: 要使节点符合静态根的条件,它应该具有不仅仅是静态文本的子节点。
      否则,静态的成本将超过始终保持新鲜的成本。
      **/
     <div>非静态根节点</div> 
     <button @click="clickBtn">{{ count }}</button>
</div>

静态节点编译成staticRenderFns函数:

// 判断是否是静态根节点
function markStaticRoots (node, isInFor) {
    if (node.type === 1) {
      if (node.static || node.once) {
        node.staticInFor = isInFor;
      }
      // For a node to qualify as a static root, it should have children that
      // are not just static text. Otherwise the cost of hoisting out will
      // outweigh the benefits and it's better off to just always render it fresh.
      if (node.static && node.children.length && !(
        node.children.length === 1 &&
        node.children[0].type === 3
      )) {
        node.staticRoot = true;
        return
      } else {
        node.staticRoot = false;
      }
      if (node.children) {
        for (var i = 0, l = node.children.length; i < l; i++) {
          markStaticRoots(node.children[i], isInFor || !!node.for);
        }
      }
      if (node.ifConditions) {
        for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
          markStaticRoots(node.ifConditions[i$1].block, isInFor);
        }
      }
    }
  }
  
  
// 如果是静态根节点则执行genStatic
function genElement (el, state) {
    if (el.staticRoot && !el.staticProcessed) {
      return genStatic(el, state)
    }
}
  
  // 静态根节点生成至staticRenderFns函数中
  // hoist static sub-trees out
  function genStatic (el, state) {
    el.staticProcessed = true;
    // Some elements (templates) need to behave differently inside of a v-pre
    // node.  All pre nodes are static roots, so we can use this as a location to
    // wrap a state change and reset it upon exiting the pre node.
    var originalPreState = state.pre;
    if (el.pre) {
      state.pre = el.pre;
    }
    state.staticRenderFns.push(("with(this){return " + (genElement(el, state)) + "}"));
    state.pre = originalPreState;
    return ("_m(" + (state.staticRenderFns.length - 1) + (el.staticInFor ? ',true' : '') + ")")
  }

生成的render函数如下:

function render() {
  with(this) {
    return _c('div', {
      attrs: {
        "id": "app"
      }
    }, [_m(0), _c('div', [_v("非静态根节点")]), _c('button', {
      on: {
        "click": clickBtn
      }
    }, [_v(_s(count))])])
  }
}

_m = renderStatic()

render函数生成vnode阶段

// 静态根节点只需要生成一次Vnode,生成后会缓存起来,更新的时候直接从缓存中获取
function renderStatic (
    index,
    isInFor
  ) {
    var cached = this._staticTrees || (this._staticTrees = []);
    var tree = cached[index];
    // if has already-rendered static tree and not inside v-for,
    // we can reuse the same tree.
    if (tree && !isInFor) {
      return tree
    }
    // otherwise, render a fresh tree.
    tree = cached[index] = this.$options.staticRenderFns[index].call(
      this._renderProxy,
      null,
      this // for render fns generated for functional component templates
    );
    // 标记该节点为静态节点
    markStatic(tree, ("__static__" + index), false);
    return tree
  }
// this.$options.staticRenderFns[0]

(function anonymous(
) {
    with(this){return _c('div',[_c('span',[_v("静态根节点")])])}
})

diff阶段

 function patchVnode (
      oldVnode,
      vnode,
      insertedVnodeQueue,
      ownerArray,
      index,
      removeOnly
    ) {
      // 对于静态根节点,此时就直接结束对比了
      if (oldVnode === vnode) {
        return
      }
      }