工作中需要使用左边导航栏,右侧窗口切换时,地址会变,左侧导航栏需要匹配相应的菜单并高亮展开。 组件结构基本如下,每次点击菜单展开折叠时,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)
)
)
)
}