阅前须知: 本文介绍的插件仅适用于react-router@6
(适用于vue-router@4
特性的仍在开发中)
何为约定式路由
约定式路由也叫文件路由,就是不需要手写路由配置,文件系统即路由,通过目录和文件及其命名分析出路由配置
举个例子来说,如果你的页面文件结构长下面的这个样子:
└── pages
├── index.tsx
└── users.tsx
则对应生成的路由列表如下所示:
[
{ path: "/", element: <PagesIndex /> },
{ path: "users", element: <PagesUsers /> },
];
通常这个生成过程是由搭建框架来实现的,如umi
、next
,当然也有独立的依赖于特定开发框架的库,例如基于vite
的 vite-plugin-pages
和 unplugin-vue-router
。
使用约定式路由的好处
当在一个项目尤其是大型项目中使用约定式路由时,有以下好处:
- 如果某个页面出错或需要优化时,可以根据页面 url 迅速定位要编码的页面文件
- 开发者在开发新页面时只需专注于页面的编码,无需到路由代码文件中注册路由
约定式路由存在的局限
但目前约定式路由并没有在开发中得到广泛的推广,主要有以下原因:
-
约定式路由局限于搭建或开发框架:知名的搭建框架如
next
和umi
都是内部实现了约定式路由的功能,不需要我们去寻找对应的约定式路由插件。但不是每个项目都基于next
和umi
进行开发的。如果使用了开发框架如vite
、webpack
或者parcel
等,则需要我们去找能对应开发框架的插件去实现该功能。 -
难以应对稍微复杂场景:
-
场景 1:需要在对应路由上补充属性:
一个
react-router@6
的路由对象的ts
结构如下所示:interface RouteObject { path?: string; index?: boolean; children?: React.ReactNode; caseSensitive?: boolean; id?: string; loader?: LoaderFunction; action?: ActionFunction; element?: React.ReactNode | null; Component?: React.ComponentType | null; errorElement?: React.ReactNode | null; ErrorBoundary?: React.ComponentType | null; handle?: RouteObject["handle"]; shouldRevalidate?: ShouldRevalidateFunction; lazy?: LazyRouteFunction<RouteObject>; }
如果我们要在对应的路由上设置属性如
errorElement
、loader
,其复杂程度需要看实现约定式路由的插件,有些插件仅支持设置基础数据类型的路由属性如caseSensitive
、lazy
。 -
场景 2:需要在对应路由上补充属性:
在开发中我们需要根据角色的权限去分配其能访问的路由,如下所示:
import { useSelector } from "react-redux"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; // 普通路由列表 const basicRoutes = [ // ... ]; // 高级路由列表,仅admin可以访问 const advancedRoutes = [ // ... ]; export default function App() { // 从Redux Store中获取用户角色 const role = useSelector((state) => state.user.role); const router = useMemo(() => { // 根据角色分配可访问的路由 if (role === "amdin") { return createBrowserRouter([...basicRoutes, ...advancedRoutes]); } else { return createBrowserRouter([...basicRoutes]); } }, [role]); return <RouterProvider router={router} />; }
在上述例子中,如果角色
role
为admin
的用户可以访问basicRoutes
和advancedRoutes
的路由,否则只能访问basicRoutes
的路由。在这种场景下,有些约定式路由插件如xx
会导出生成的路由列表routes
,我们可以对此进行编码以拆分成两种权限的路由,但有些不导出路由列表的插件则需要我们从另外的角度去实现权限路由的功能。
-
因此,我开发了这款基于Vscode
的约定式路由插件
这款插件的名字叫Sagaroute
,大家在Vscode Extension Marketplace
中搜索即可下载:
运行效果图:
他具有以下基础特性:
- 🌴 泛用性: 基于
Vscode
实现,且生成的代码遵循ES6 Module
格式,适用于任何开发环境 - 📲 实用性: 采用近似于
umi
的约定式路由规则,更贴近实际开发场景 - 📇 样式一致: 生成代码保存后会自动触发代码风格约束插件的格式化(如
prettier
、eslint
,取决于vscode
安装了哪些插件) - 🛠️ 可配置性:支持配置文件,提供实用的配置项
还有更多高级特性会在下文讲解,接下来先说一下使用方法
使用方法
1. 安装插件
从vscode
的EXTENSTIONS: MARKETPLACE
中下载,如下所示 👇:
下载Sagaroute
后会发现Vscode
右下角的状态栏出现了一个如下的控件:
Sagaroute
在每个项目中是默认不开启监听工作的,需要开发者手动点击上面 👆 的控件切换监听状态,当开启监听后控件会如下所示
2. 在项目中下载@sagaroute/react
(非必须但推荐)
npm install @sagaroute/react
与prettier-vscode
一样,在sagaroute-vscode
中有内嵌的@sagaroute/react
,因此即使不做这一步也不会影响运行。但这里推荐做做这一步是因为这样确保不同开发者在同一项目中使用的@sagaroute/react
的版本是一致的,避免因版本不同导致的差异化情况
执行该步骤后需要重启Vscode
才会生效
3. 在路由模板文件中用注释做标记注入
路由模板文件是指要被注入路由列表的文件,我们需要通过注释来指明路由模板文件中哪个位置被注入路由列表和依赖
例如存在路由模板文件,其内容如下:
import React from "react";
const routes = [];
const router = createBrowserRouter(routes);
export default router;
我们需要对上述文件用注释进行标记,标记后如下所示:
import React from "react";
import { createBrowserRouter } from "react-router-dom";
/* sagaroute-inject:imports */
/* sagaroute-inject:routes */
const routes = [];
const router = createBrowserRouter(routes);
export default router;
其中/* sagaroute-inject:imports */
用于标记依赖注入的位置,/* sagaroute-inject:routes */
用于标记路由列表注入的位置。
4. 生成路由列表
@sagaorute/vscode-extension
会监听页面文件目录里的文件,当更改的文件CRTL+S
保存时开始执行生成路由,同时你也可以使用命令要求本插件开始生成路由,即(CMD/CTRL + Shift + P)唤出命令面板后输入Sagaroute: routing
,如下 👇 所示:
使用方法介绍完毕,接下来说一下这个插件高级在哪些方面:
高级特性介绍
1. 支持任意类型的路由属性设置
你可以在组件的routeProps
字段中设置属性,routeProps
上的所有属性会复制到注册路由上:
假如存在src/pages/users.tsx
文件,其文件内容如下所示:
import ErrorBoundary from "@/components/ErrorBoundary";
export default function Users() {
return <div>Users...</div>;
}
// 设置routeProps
/** @type {import('react-router-dom').RouteObject} */
Users.routeProps = {
caseSensitive: false,
};
生成的注册路由如下所示:
{
path:'user',
element:<PagesUsers/>,
caseSensitive: false,
ErrorBoundary: ComponentsErrorBoundary
}
可看以下效果图:
routeProps
属性的设置值支持任意类型,不过要遵循编码规则,详情请看此处
2. 提供钩子函数
Sagaroute
在每次生成路由的执行过程中会经历如下图 👇 所示的三个阶段:
每个阶段都有对应的钩子函数,如下图 👇 所示
利用这些钩子函数,可以实现以下场景需求:
3. lazy
路由的批量生成
lazy
是react-router@6.4
新增的路由属性,用于路由文件的懒加载,lazy
有多种写法,如下所示:
[
// 写法1: 只对路由文件进行懒加载
{
path: "projects",
loader: ({ request }) => fetchDataForUrl(request.url),
lazy: () => import("./projects"),
},
// 写法2: 对路由文件及其路由属性变量进行懒加载
{
path: "messages",
async lazy() {
let { messagesLoader, Messages } = await import("./pages/Dashboard");
return {
loader: messagesLoader,
Component: Messages,
};
},
},
];
本插件可以通过设置lazy
配置项统一生成上述 👆 第 2 种写法的lazy
路由,如下 👇 效果图:
4. 路由对象的缓存
本插件内部实现了路由对象的缓存机制,因此存在以下优点:
- 加速二次生成路由的速度:对内容未更改的文件会直接取缓存作为生成结果,加快生成整个路由列表的生成速度
- 只在路由列表变化时更新文件:对每个非缓存的新路由,会与缓存中的路由进行对比,如果所有对比结果与上次相同且没有增删的路由,则不会更改文件内容,避免频繁的热更新
可看以下 👇 效果图:
若要无视缓存强制生成路由列表,则可使用Sagaroute: routing
命令
5. 路由路径的智能拼写
你可以在项目中通过键入"//"
,sagaroute
插件会提供所有路由的路径提示,如下所示:
选择后,"//"
会被替换成所选择的路由路径
注意:在vscode
项目首次打开时,要先做保存操作或者强制Sagaroute: routing
后,才会有开启路由路径智能拼写
本插件的可靠性
本vscode
插件包含两部分:sagaroute-vscode
和其依赖的核心库@sagaroute/react
,其测试用例情况如下 👇 所示:
sagaroute-vscode
: 含 14 个快照测试,代码覆盖率未统计(暂未找到如何在vscode extension
测试中统计代码覆盖率的方法)@sagaroute/react
: 含 85 个单例测试和快照测试,代码覆盖率 98%
以上测试用例在window
、macOS
、ubuntu
环境均可跑通,因此大家可以放心食用。
关于流水线的运行详情请看github action workflow
额外开发的@sagaroute/cmd
插件
初次之外,我还开发了一个用于快速生成约定式路由的命令行插件@sagaroute/cmd
,其运行效果如下所示:
大家也可以下载来玩玩。