引言
在上一篇文章《🧩 vue-router 动态页缓存异常处理方案解析》中,我们探讨了 Vue Router 动态页缓存的异常处理方案。如今,vue-router-better-view 又新增一项关键特性 :扁平化嵌套路由渲染。本文将深入解析该特性的实现原理及应用场景。
背景问题
在中后台开发中,列表页与表单页的联动是高频场景。例如:
- 用户列表页点击项跳转详情页
- 商品列表页进入编辑页
此时我们可能会编写出如下的 routes
:
ts
// src/router/index.ts
const routes: RouteRecordRaw[] = [
{
path: '/system',
component: Layout, // 布局组件
children: [
{
path: 'user',
component: UserList, // 用户列表组件
children: [
{
path: 'detail/:id?',
component: UserDetail, // 用户详情组件
},
],
},
],
},
]
此时当我们信心满满的开始访问 /system/user/detail
时却会发现页面一直处于 UserList
组件中,why?
可能有人会说官方给出的嵌套路由方案需要同时使用嵌套 RouterView
,比如在 UserList
中增加子页面渲染的 RouterView
逻辑或者将子页面与列表页同级注册到布局组件中,其中同级注册应该是我们最常用的解决方案了,但是同样存在以下问题:
方案类型 | 优点 | 缺点 |
---|---|---|
嵌套 RouterView |
保持路由层级关系 | 需手动嵌套多个 <RouterView> ,代码冗余 |
同级注册 | 简化组件层级 | 丢失 route.matched 的路由匹配信息,而且定义也不符合直觉 |
本文将深入探讨如何通过 Vue Router 的 viewDepth
机制 和 官方导出的内部 key ,实现 扁平化嵌套路由渲染 ,让上面符合开发直觉的嵌套路由渲染不再是问题。
问题解决:如何实现精准渲染?
RouterView
的渲染机制分析
为了方便观看删除了一些无关代码!
ts
const RouterViewImpl = defineComponent({
setup(props, { attrs, slots }) {
const injectedRoute = inject(routerViewLocationKey);
const routeToDisplay = computed(() => props.route || injectedRoute.value);
// 重点:注入视图深度
const injectedDepth = inject(viewDepthKey, 0);
// 计算当前深度,跳过无组件的父路由
const depth = computed(() => {
let initialDepth = unref(injectedDepth);
const { matched } = routeToDisplay.value;
let matchedRoute;
// 这里就是为什么官方仅支持不携带组件的父级路由
while ((matchedRoute = matched[initialDepth]) && !matchedRoute.components) {
initialDepth++;
}
return initialDepth;
});
// 提供当前深度 +1 给下一个嵌套的 RouterView
provide(viewDepthKey, computed(() => depth.value + 1));
...
},
});
上面代码介绍了 RouterView
是如何进行视图组件渲染的,里面的 viewDepthKey
起到了关键作用,我们再看看 viewDepthKey
的介绍。
ts
/**
* Allows overriding the router view depth to control which component in
* `matched` is rendered. rvd stands for Router View Depth
*
* @internal
*/
const viewDepthKey = Symbol('router view depth' );
上面的注释通过翻译后大概意思是:允许覆盖路由视图的深度,以控制 matched
中的哪个组件会被渲染。
接下来思路就比较明确了,可以通过导出的 viewDepthKey
对 RouterView
内部渲染的视图深度进行精准控制。
实现精准渲染
html
<script setup lang="ts">
import { provide, computed, inject } from 'vue'
import { viewDepthKey, useRoute } from 'vue-router'
const injectedViewDepth = inject(viewDepthKey, 0)
const route = useRoute()
// 直接提供当前路由最后一个匹配项的视图索引
provide(viewDepthKey, computed(() => route.matched.length - 1))
</script>
<template>
<main>
<RouterView />
</main>
</template>
通过库实现
html
<script setup lang="ts">
import { BetterRouterView, useExactView } from 'vue-router-better-view'
// 若不想使用 BetterRouterView 时,也可通过提供的 useExactView 函数进行实现
// useExactView({ exact: true })
</script>
<template>
<main>
<BetterRouterView exact />
<!-- 在使用 useExactView 时可以用原生的 RouterView -->
<!-- <RouterView /> -->
</main>
</template>
结语
通过 viewDepthKey
机制,我们可以突破 Vue Router 原生嵌套路由的限制,实现更灵活的扁平化渲染。vue-router-better-view
将这一能力封装为开箱即用的组件,显著提升了代码简洁性与可维护性。