el-menu-item每次点击后都mount

1,746 阅读2分钟

工作中需要使用左边导航栏,右侧窗口切换时,地址会变,左侧导航栏需要匹配相应的菜单并高亮展开。 组件结构基本如下,每次点击菜单展开折叠时,NavItem都会重新mount,查看elementUI源码,原来是ElMenu的key设置为 key={ +this.collapse },这样vue在patch时发现key不一样就会直接重新创建一个新的ElMenu,所有的子组件也会重建。在NavItem组建中会维护一个needValidate的状态,每次mount后又都初始化了一遍。ElMenu设置key的原因是什么的?

<template>
  <el-row class="tac">
    <el-menu
      class="el-menu-vertical-demo"
      :collapse="isCollapse"
      :collapse-transition="false"
    >
      <nav-item :menu="navList" />
    </el-menu>
  </el-row>
</template>
<script>
import { mapState } from "vuex";
import NavItem from "./NavItem2.vue";
export default {
  name: "LeftNav",
  components: { NavItem },
  data() {
    NavItem;
    return {
      navList: [
        { path: "/index1", label: "导航一", icon: "el-icon-location" },
        { path: "/index2", label: "导航二", icon: "el-icon-menu" },
        { path: "/index3", label: "导航三", icon: "el-icon-document" },
      ],
    };
  },
  methods: {},
  computed: {
    ...mapState(["isCollapse"]),
  },
  mounted() {
    console.log("nav2 mounted");
  },
  updated() {
    console.log("nav2 updated");
  }
};
</script>
<template>
  <div>
      <el-menu-item
        v-for="item in menu"
        :class="{ 'is-active': vaildAvtive(item) }"
        :index="item.path"
        :key="item.path"
        @click="click()"
      >
        <i :class="item.icon" />
        <span slot="title">{{ item.label }}</span>
      </el-menu-item>
  </div>
</template>
<script>
export default {
  name: "NavItem",
  props: ["menu", "collapse"],
  data() {
    return {needValidate: false};
  },
  methods: {
    click() {
      this.$store.commit("changeCollapse");
    },
    vaildAvtive(){
    }
  },
  mounted() {
    console.log("nav2 item mounted");
  }
};
</script>
// \element-ui\packages\menu\src\menu.vue
export default {
  name: 'ElMenu',

  render (h) {
    const component = (
      <ul
        role="menubar"
        key={ +this.collapse } //每次切换折叠时可以就会改变所有的子节点都会重新destroy新的的子节点mount
        style={{ backgroundColor: this.backgroundColor || '' }}
        class={{
          'el-menu--horizontal': this.mode === 'horizontal',
          'el-menu--collapse': this.collapse,
          "el-menu": true
        }}
      >
        { this.$slots.default }
      </ul>
    );
...
}
 return function patch (oldVnode, vnode, hydrating, removeOnly) {
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
      return
    }

    var isInitialPatch = false;
    var insertedVnodeQueue = [];

    if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
      isInitialPatch = true;
      createElm(vnode, insertedVnodeQueue);
    } else {
      var isRealElement = isDef(oldVnode.nodeType);
      // 判断两个Vnode是不是相同的
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
      // 如果相同则进行比较
        // patch existing root node
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly);
      } else {
      // 如果不同则会删除oldVnode,创建并插入新的vnode
        if (isRealElement) {
          // mounting to a real element
          // check if this is server-rendered content and if we can perform
          // a successful hydration.
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
            oldVnode.removeAttribute(SSR_ATTR);
            hydrating = true;
          }
          if (isTrue(hydrating)) {
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              invokeInsertHook(vnode, insertedVnodeQueue, true);
              return oldVnode
            } else if (process.env.NODE_ENV !== 'production') {
              warn(
                'The client-side rendered virtual DOM tree is not matching ' +
                'server-rendered content. This is likely caused by incorrect ' +
                'HTML markup, for example nesting block-level elements inside ' +
                '<p>, or missing <tbody>. Bailing hydration and performing ' +
                'full client-side render.'
              );
            }
          }
          // either not server-rendered, or hydration failed.
          // create an empty node and replace it
          oldVnode = emptyNodeAt(oldVnode);
        }

        // replacing existing element
        var oldElm = oldVnode.elm;
        var parentElm = nodeOps.parentNode(oldElm);

        // create new node
        createElm(
          vnode,
          insertedVnodeQueue,
          // extremely rare edge case: do not insert if old element is in a
          // leaving transition. Only happens when combining transition +
          // keep-alive + HOCs. (#4590)
          oldElm._leaveCb ? null : parentElm,
          nodeOps.nextSibling(oldElm)
        );

        // update parent placeholder node element, recursively
        if (isDef(vnode.parent)) {
          var ancestor = vnode.parent;
          var patchable = isPatchable(vnode);
          while (ancestor) {
            for (var i = 0; i < cbs.destroy.length; ++i) {
              cbs.destroy[i](ancestor);
            }
            ancestor.elm = vnode.elm;
            if (patchable) {
              for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
                cbs.create[i$1](emptyNode, ancestor);
              }
              // #6513
              // invoke insert hooks that may have been merged by create hooks.
              // e.g. for directives that uses the "inserted" hook.
              var insert = ancestor.data.hook.insert;
              if (insert.merged) {
                // start at index 1 to avoid re-invoking component mounted hook
                for (var i$2 = 1; i$2 < insert.fns.length; i$2++) {
                  insert.fns[i$2]();
                }
              }
            } else {
              registerRef(ancestor);
            }
            ancestor = ancestor.parent;
          }
        }

        // destroy old node
        if (isDef(parentElm)) {
          removeVnodes([oldVnode], 0, 0);
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode);
        }
      }
    }

    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
    return vnode.elm
  }
}

function sameVnode (a, b) {
  return (
    // 首先会比较key
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}