@vlian/router
v1.0.16
Published
基于 React Router 的路由管理包,支持动态路由、权限控制、路由转换等功能
Downloads
1,349
Maintainers
Readme
@vlian/router
基于 React Router 的高级路由管理包,支持动态路由、权限控制、路由转换等功能。
📦 安装
npm install @vlian/router
# 或
pnpm add @vlian/router
# 或
yarn add @vlian/router🔧 依赖要求
本项目需要以下 peer dependencies:
react: ^19.2.0react-dom: ^19.2.0react-router-dom: ^7.0.0@ant-design/cssinjs: >=1.24.0classnames: >=2.3.0
✨ 特性
- 🚀 动态路由加载 - 支持从 API 动态获取路由配置
- 🔐 权限控制 - 支持基于角色和登录状态的权限控制
- 🎨 布局系统 - 灵活的布局配置,支持嵌套布局
- ⚡ 懒加载 - 路由组件自动懒加载,优化首屏性能
- 🔄 路由转换 - 将自定义路由格式自动转换为 React Router 格式
- 📱 子应用支持 - 支持微前端子应用模式
- 🎯 类型安全 - 完整的 TypeScript 类型定义
- 🎭 加载状态 - 内置加载动画,支持自定义加载组件
- 🛡️ 错误处理 - 支持自定义错误边界组件
- 📋 菜单生成 - 自动从路由配置生成菜单数据
📖 使用指南
基础用法
import { RouterProvider } from '@vlian/router';
import type { RouteItem } from '@vlian/router';
// 定义路由组件导入映射
const imports = {
// 页面组件
pages: {
root: () => import('./pages/Root'),
home: () => import('./pages/Home'),
about: () => import('./pages/About'),
'404': () => import('./pages/NotFound'),
},
// 布局组件
layouts: {
root: () => import('./layouts/RootLayout'),
admin: () => import('./layouts/AdminLayout'),
},
// 加载组件
loadings: {
root: () => import('./components/Loading'),
},
// 错误组件
errors: {
root: () => import('./components/ErrorBoundary'),
},
};
// 获取动态路由的函数
const fetchRoutes = async (): Promise<RouteItem[]> => {
const response = await fetch('/api/routes');
return response.json();
};
function App() {
return (
<RouterProvider
fetchRoutes={fetchRoutes}
imports={imports}
/>
);
}路由配置格式
路由配置使用 RouteItem 类型,支持以下字段:
type RouteItem = {
// 布局组件的 key(对应 imports.layouts 中的键)
layout?: string | null;
// 页面组件的 key(对应 imports.pages 中的键)
page?: string | null;
// 加载组件的 key(对应 imports.loadings 中的键)
loading?: string | null;
// 错误组件的 key(对应 imports.errors 中的键)
errors?: string | null;
// 路由名称,唯一标识
name: string;
// 路由路径
path: string | undefined;
// 是否为分组路由(不渲染自身,只作为容器)
isGroup?: boolean;
// 是否启用重定向到第一个子路由
enableRedirection?: boolean;
// 路由元数据
handle: RouteItemHandle;
// 子路由
children?: RouteItem[];
};
type RouteItemHandle = {
// 路由标题
title: string;
// 国际化 key
i18nKey?: string;
// 排序顺序
order: number;
// 图标
icon?: string;
// 在菜单中隐藏
hideInMenu?: boolean;
// 隐藏页脚
hideFooter?: boolean;
// 保活(Keep Alive)
keepAlive?: boolean;
// 是否需要登录
needLogin?: boolean;
// 允许访问的角色列表
roles?: string[];
// 其他自定义字段
[key: string]: unknown;
};路由配置示例
const routes: RouteItem[] = [
{
name: 'home',
path: '/home',
page: 'home',
layout: 'root',
handle: {
title: '首页',
order: 1,
icon: 'home',
needLogin: true,
},
},
{
name: 'admin',
path: '/admin',
layout: 'admin',
isGroup: true,
enableRedirection: true,
handle: {
title: '管理后台',
order: 2,
icon: 'admin',
roles: ['admin'],
},
children: [
{
name: 'admin-users',
path: '/admin/users',
page: 'adminUsers',
handle: {
title: '用户管理',
order: 1,
},
},
{
name: 'admin-settings',
path: '/admin/settings',
page: 'adminSettings',
handle: {
title: '系统设置',
order: 2,
},
},
],
},
];使用路由 Hook
import { useRoute } from '@vlian/router';
function MyComponent() {
const route = useRoute();
// route 包含以下信息:
// - id: 路由 ID
// - pathname: 当前路径
// - search: 查询字符串
// - hash: 哈希值
// - query: 解析后的查询参数对象
// - fullPath: 完整路径(pathname + search + hash)
// - handle: 路由元数据
// - data: 路由数据(来自 loader)
return (
<div>
<h1>{route.handle?.title}</h1>
<p>当前路径: {route.pathname}</p>
<p>查询参数: {JSON.stringify(route.query)}</p>
</div>
);
}使用导航工具
在子应用模式下,可以通过 window.$router 访问导航工具:
// 导航到指定路径
window.$router.push('/home');
// 带查询参数导航
window.$router.push('/users', { page: 1, size: 10 });
// 替换当前历史记录
window.$router.replace('/login');
// 返回上一页
window.$router.back();
// 前进
window.$router.forward();
// 跳转到首页
window.$router.goHome();
// 向上导航(父路由)
window.$router.navigateUp();
// 重新加载
window.$router.reload();
// 使用 React Router 的 navigate 方法
window.$router.navigate('/path', { replace: true });生成菜单数据
import { formatMenusDate } from '@vlian/router';
import { useRoute } from '@vlian/router';
function Menu() {
const route = useRoute();
// 假设你有路由数据
const routes = route.dynamicRoutes || [];
const menus = formatMenusDate(routes);
return (
<ul>
{menus.map(menu => (
<li key={menu.key}>
<a href={menu.path}>{menu.name}</a>
{menu.children && (
<ul>
{menu.children.map(subMenu => (
<li key={subMenu.key}>
<a href={subMenu.path}>{subMenu.name}</a>
</li>
))}
</ul>
)}
</li>
))}
</ul>
);
}RouterProvider 配置选项
<RouterProvider
// 必需:获取动态路由的函数
fetchRoutes={fetchRoutes}
// 必需:组件导入映射
imports={imports}
// 可选:根路由配置(默认使用内置配置)
rootRoutes={customRootRoutes}
// 可选:静态路由(默认包含 404 路由)
staticRoutes={customStaticRoutes}
// 可选:React Router 配置选项
options={{
future: {
v7_startTransition: true,
v7_relativeSplatPath: true,
},
}}
// 可选:是否为子应用模式(默认 false)
isSubApp={false}
// 可选:自定义加载组件
loadingRender={<CustomLoading />}
// 可选:加载动画大小(默认 '24px')
loadingSize="32px"
// 可选:加载动画颜色(默认 'currentColor')
loadingColor="#1890ff"
// 可选:加载动画透明度(默认 0.25)
loadingOpacity={0.3}
/>🎨 组件导出
RouterProvider
路由提供者组件,用于初始化和管理路由系统。
Hooks
useRoute<T>()- 获取当前路由信息useRouterTransform()- 路由转换 Hook(内部使用)
工具函数
navigator(router)- 创建导航工具对象formatMenusDate(routes)- 将路由配置格式化为菜单数据
🔍 类型定义
主要类型定义:
RouteItem- 路由项类型RouteItemHandle- 路由元数据类型RouteMapType- 路由组件映射类型RouterProviderProps- RouterProvider 属性类型RouterContextType- 路由上下文类型FormattedMenuItem- 格式化后的菜单项类型
🚀 高级用法
自定义根路由
import { RouteItem } from '@vlian/router';
const customRootRoutes: RouteItem = {
name: 'root',
path: '/',
page: 'root',
layout: 'customRoot',
handle: {
title: '应用根',
order: 0,
},
children: [],
};
<RouterProvider
fetchRoutes={fetchRoutes}
imports={imports}
rootRoutes={customRootRoutes}
/>自定义静态路由
import { RouteItem } from '@vlian/router';
const customStaticRoutes: RouteItem[] = [
{
name: '403',
path: '/403',
page: 'forbidden',
handle: {
title: '无权限',
order: 99999997,
},
},
{
name: '404',
path: '/404',
page: 'notFound',
handle: {
title: '页面不存在',
order: 99999998,
},
},
{
name: 'notFound',
path: '*',
page: 'notFound',
handle: {
title: '页面不存在',
order: 99999999,
},
},
];
<RouterProvider
fetchRoutes={fetchRoutes}
imports={imports}
staticRoutes={customStaticRoutes}
/>子应用模式
在微前端场景中,可以启用子应用模式,通过 window.$router 访问路由导航:
<RouterProvider
fetchRoutes={fetchRoutes}
imports={imports}
isSubApp={true}
/>📝 注意事项
路由名称唯一性:每个路由的
name字段必须唯一,它作为路由的唯一标识符。组件导出格式:所有通过
imports导入的组件必须使用默认导出,例如:// ✅ 正确 export default function MyPage() { ... } // ❌ 错误 export function MyPage() { ... }布局和页面:
- 如果路由有
layout且不是分组路由,会使用布局组件包裹页面 - 如果路由有
page且有子路由,会自动创建 index 路由来渲染页面 - 分组路由(
isGroup: true)不会渲染自身,只作为子路由的容器
- 如果路由有
路由排序:子路由会根据
handle.order自动排序,数值越小越靠前。权限控制:当前版本的路由转换支持权限相关的元数据配置,但具体的权限验证逻辑需要在业务层面实现。
🤝 贡献
欢迎提交 Issue 和 Pull Request!
📄 许可证
MIT
