在 vue-router 的使用中,有时会面临这样一个问题,那就是 vue-router 的设计要求 component
层级和 route
层级保持一致。
const route = {
path: '/list',
name: 'list',
component: List,
children: [{
path: 'detail/:id',
name: 'detail',
component: Detial,
}]
}
上面的这种常见的路由系统,当访问detail二级路由时,List
和 Detail
组件会同时渲染至页面中。
但是有些时候我们不需要List
和 Detail同事渲染,只需要渲染一个。
这个问题初看很简单,如果只是为了达到 List
和 Detail
同时只有一个存在,只需将路由重新设计为扁平化的即可。然而这会破坏这两个路由本身所拥有的父子语义,因为 UI 的表现而去破坏语义是不理想的。其次,我们项目中还有一个根据路由层级自动生成的面包屑导航,破坏 list
和 detail
的父子关系会导致面包屑导航无法正确工作。
+---------------------+
| 当前最深匹配的路由 |
| 是否与 |
| 自身路由匹配 |
+---------------------+
|
yes | no
+--------+--------*
| |
v v
+------+ +------------+
| show | | show |
| self | | child route|
+------+ +------------+
$route.matched
存放着当前所有匹配的路由, 中的最后一个项就是当前匹配层次最深的路由。我们可以通过路有对象的 instances.default
取得与此路由相关联的 component
实例,然后与当前组件实例比较一下即可。最后我们通过手写 vue
的 render
方法来完成条件渲染。
最终,我们采取了高阶组件的实现方式,为组件提供子路由匹配时,将自己”隐藏“的功能。
代码实现
export default function routeReplaceSelf(component) {
return {
name: 'routerReplaceSelf',
computed: {
showChild() {
const deepestMatchedRoute = this.$route.matched[this.$route.matched.length - 1];
return deepestMatchedRoute.instances.default !== this;
},
},
render(h) {
return this.showChild ? h('router-view') : h(component);
},
};
}
使用方法
routes: [
{
path: '/',
redirect: '/list'
},
{
path: '/list',
name: 'List',
component: routeReplaceSelf(List),
children: [
{
path: 'detail/:id',
name: 'Detail',
props: true,
component: Detail
}
]
}
]
还有一个小问题,当从子路由返回父组件时,父组件会重新 mount
。这里可以借助 keep-alive
来缓存组件避免不必要的 mount
。
render(h) {
const child = this.showChild ? h('router-view') : h(component);
return h('keep-alive', [child]);
},