受 Vue Router 启发的 React 路由库。Vue Router 风格守卫、Koa 风格中间件、内置 LRU 缓存,Router 实例即全局 API
| 特性 | @jl-org/react-router | React Router | TanStack Router |
|---|---|---|---|
全局守卫 beforeEach/beforeResolve/afterEach |
✅ Vue Router 风格全局 API | route.beforeLoad 路由 / 子树级 |
|
| Koa 风格中间件 | ✅ route middlewares,(ctx, next) |
✅ middleware / clientMiddleware |
beforeLoad + loader 生命周期 |
| 内置组件 keep-alive / LRU 页面缓存 | ✅ 组件实例级 LRU keep-alive | ❌ 无内置组件 keep-alive | |
全局布局 layouts |
✅ 集中配置 pathname 匹配规则 |
pnpm i @jl-org/react-router
# or npm i @jl-org/react-router
# or yarn add @jl-org/react-routerimport { lazy } from 'react'
import { RouterProvider, Outlet, createBrowserRouter } from '@jl-org/react-router'
const router = createBrowserRouter({
routes: [
{ path: '/', component: lazy(() => import('./views/home')) },
{
path: '/dashboard',
component: lazy(() => import('./views/dashboard')),
meta: { title: 'Dashboard', requiresAuth: true },
middlewares: [
async (ctx, next) => {
if (ctx.meta?.requiresAuth && !getUser()) { ctx.redirect('/login'); return }
await next()
},
],
},
],
options: {
cache: { limit: 5, exclude: ['/login'] },
beforeEach: async (to, _from, next) => next(),
afterEach: (to) => { document.title = to.meta?.title ?? 'App' },
},
})
export function App() {
return <RouterProvider router={router}><Outlet /></RouterProvider>
}
// Router 实例即全局 API
router.navigate('/dashboard')
router.replace('/login')createBrowserRouter({
routes: RouteObject[],
options: {
base?: string,
cache?: boolean | {
limit?: number, // @default 10
include?: (string | RegExp)[], // 不传则缓存所有路径,传空数组则不缓存
exclude?: (string | RegExp)[], // 优先于 include,命中则不缓存
},
cacheKey?: (loc: LocationLike) => string,
loadingComponent?: ReactElement | ComponentType,
notFoundComponent?: ReactElement | ComponentType,
layouts?: LayoutConfig[], // 全局布局
beforeEach?: NavigationGuard,
beforeResolve?: NavigationGuard,
afterEach?: AfterEachGuard,
},
})cache 开启后,命中的页面会以 keep-alive 形式保留组件状态。需要在退出登录、切换租户、切换账号等场景释放缓存时,可以直接调用 Router 实例方法:
// 清空所有 keep-alive 页面缓存
router.clearCache()
// 只删除某个缓存 key
router.deleteCache('/cards')
// 按正则或函数删除一组缓存 key
router.deleteCache(/^\/cards/)
router.deleteCache(key => key.includes(':guest'))如果清缓存后会立刻跳转,推荐在目标路由进入后再清,避免当前可缓存页面在同一次 render 中重新写回缓存:
router.afterEach((ctx) => {
if (ctx.to.pathname === '/login')
router.clearCache()
})| 字段 | 说明 |
|---|---|
path |
路径 |
component |
组件或 lazy() |
children |
嵌套路由 |
meta |
自定义信息 |
middlewares |
Koa 风格 (ctx, next) |
loadingComponent |
懒加载占位,优先于全局 |
layoutComponent |
路由级布局(包裹当前路由) |
按 pathname 匹配,第一个命中的布局包裹渲染结果。exclude 优先;include 为空则匹配全部
const router = createBrowserRouter({
routes: [...],
options: {
layouts: [
{
component: MainLayout, // 接收 children 的布局组件
include: ['/dashboard', '/users'],
exclude: ['/login'],
},
{
component: AdminLayout,
include: ['/admin'],
},
],
},
})| 字段 | 说明 |
|---|---|
component |
布局组件,({ children }) => ReactNode |
include |
命中任一则使用;空则匹配全部 |
exclude |
命中任一则跳过此布局 |
options: {
notFoundComponent: () => <div>页面不存在</div>,
}| 方法 | 说明 |
|---|---|
router.navigate(path) |
推入历史,触发守卫/中间件 |
router.replace(path) |
替换当前 |
router.back() |
history.back() |
router.getLocation() |
当前 LocationLike |
router.beforeEach/beforeResolve/afterEach(handler) |
守卫注册 |
router.clearCache() |
清空所有 keep-alive 页面缓存 |
router.deleteCache(matcher) |
删除匹配指定 key 的 keep-alive 页面缓存,支持 string / RegExp / function |
router.subscribe(listener) |
监听 location |
router.dispose() |
清理 |
| 名称 | 说明 |
|---|---|
<RouterProvider router> |
入口 |
<Outlet /> |
嵌套出口 |
<Link /> / <NavLink /> |
导航 |
useRouter() |
实例 |
useNavigate() |
navigate 函数 |
useLocation() |
全局当前 pathname、search、hash,默认跟随真实路由切换 |
useLocation({ scope: 'cache' }) |
当前 keep-alive 缓存 entry 的 pathname、search、hash |
useParams() |
{ params, query, hash } |
useRouteKeepAliveEffect(effect) |
keep-alive 缓存页的可见性感知 useEffect:激活时跑 effect,失活(被缓存隐藏)/ 卸载时跑其 cleanup |
开启 cache 后,离开缓存页只是把它隐藏(组件不卸载),普通 useEffect 的 cleanup 此时不会触发 —— 凡是「仅当前页可见时才该生效」的副作用 / 信号(如上报“当前在某页”、暂停视频、释放摄像头),用普通 useEffect 都会在切走后残留。useRouteKeepAliveEffect 专门解决这点:
useRouteKeepAliveEffect(() => {
reportActiveSurface(true)
return () => reportActiveSurface(false) // 隐藏 / 卸载都会复位
})- 页面激活(首次可见,或被缓存隐藏后切回)时执行
effect;失活(隐藏)或卸载时执行其返回的 cleanup - 自动解析所属缓存单元,无需手动传 key;内部用 ref 始终调用最新闭包,故不需要依赖数组
- 覆盖「晚于初次激活才挂载的后代」(如异步 /
SplitPane内晚挂载的子组件):挂载即补激活 - 未被 keep-alive 包裹时退化为普通
useEffect(挂载执行、卸载清理)
// 守卫签名
type NavigationGuard = (to, from, next) => void | Promise<void>
// next() | next(false) | next('/redirect')
// 中间件签名
type Middleware = (ctx, next) => void | Promise<void>
// ctx.redirect('/path') 可跳转src/router/ 为库源码,其余为示例:
src/router/ # 库源码
src/routes/ # 路由配置示例
src/views/ # 页面示例
src/App.tsx # Demo 入口