vue-router 高级路由设计
场景
在 vue-router 的使用中,有时会面临这样一个问题,那就是 vue-router 的设计要求 component 层级和 route 层级保持一致。
当有如下的路由定义时:
const route = {
path: '/list',
name: 'list',
component: List,
children: [{
path: 'detail/:id',
name: 'detail',
component: Detial,
}]
}
则 List 组件需在自己的模板内包含 router-view 组件,/list 与 detail/:id 路由的层级关系要在组件中体现出来。
// in List.vue
<template>
<div id="list">
<h5>This is list.</h5>
<router-view></router-view>
</div>
</template>
在这种情况下,当导航至 /list/detail/1 时, List 和 Detail 组件会同时渲染至页面中。如下:
在大多数情况下我们普遍的期望是当进入 detail 路由时,页面中只显示相应的 Detail 组件,而不在显示 List 组件。
问题
这个问题初看很简单,如果只是为了达到 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);
},
};
}
示例:
还有一个小问题,当从子路由返回父组件时,父组件会重新 mount。这里可以借助 keep-alive 来缓存组件避免不必要的 mount。
render(h) {
const child = this.showChild ? h('router-view') : h(component);
return h('keep-alive', [child]);
},
思考
在解决上述问题时,尤大在相关 issue 内说到
I think this breaks the relationship between route config nesting and router-view nesting and can make things a bit harder to reason about.
对此我是不认可的,强制路由层级和组件层级匹配,这样做增强了二者的耦合性。对路由本身而言它是没有组件这个感念的,毕竟它的名字就叫路由,而不是组件路由,页面路由。路由本身只应作为一个工具,告诉我们当前的 url 是否和其相定义相匹配,之后具体是路由到一个页面,一个组件,还是一个简单的回调则应由用户去控制。react-router (v4) 就是一个充分体现了路由灵活性的实例,借助其我们可以完成很多复杂的需求。这里推荐一下 page stack 这篇文章。