Skip to content

beixiyo/react-router

Repository files navigation

npm-version npm-download License typescript github

@jl-org/react-router

受 Vue Router 启发的 React 路由库。Vue Router 风格守卫、Koa 风格中间件、内置 LRU 缓存,Router 实例即全局 API

English | 更新日志

Code Demo

✨ 特性

特性 @jl-org/react-router React Router TanStack Router
全局守卫 beforeEach/beforeResolve/afterEach ✅ Vue Router 风格全局 API ⚠️ 无同名 API,可用 route middleware / loader 生命周期承接 ⚠️ route.beforeLoad 路由 / 子树级
Koa 风格中间件 ✅ route middlewares(ctx, next) middleware / clientMiddleware ⚠️ beforeLoad + loader 生命周期
内置组件 keep-alive / LRU 页面缓存 ✅ 组件实例级 LRU keep-alive ❌ 无内置组件 keep-alive ⚠️ 有 loader / SWR 数据缓存,非组件 keep-alive
全局布局 layouts ✅ 集中配置 pathname 匹配规则 ⚠️ 嵌套路由 / layout route ⚠️ layout / pathless layout route

📦 安装

pnpm i @jl-org/react-router
# or npm i @jl-org/react-router
# or yarn add @jl-org/react-router

🚀 快速开始

import { 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()
})

RouteObject 常用字段

字段 说明
path 路径
component 组件或 lazy()
children 嵌套路由
meta 自定义信息
middlewares Koa 风格 (ctx, next)
loadingComponent 懒加载占位,优先于全局
layoutComponent 路由级布局(包裹当前路由)

全局布局 layouts

按 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 命中任一则跳过此布局

404

options: {
  notFoundComponent: () => <div>页面不存在</div>,
}

🧭 Router API

方法 说明
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() 清理

🧩 组件 & Hooks

名称 说明
<RouterProvider router> 入口
<Outlet /> 嵌套出口
<Link /> / <NavLink /> 导航
useRouter() 实例
useNavigate() navigate 函数
useLocation() 全局当前 pathnamesearchhash,默认跟随真实路由切换
useLocation({ scope: 'cache' }) 当前 keep-alive 缓存 entry 的 pathnamesearchhash
useParams() { params, query, hash }
useRouteKeepAliveEffect(effect) keep-alive 缓存页的可见性感知 useEffect:激活时跑 effect失活(被缓存隐藏)/ 卸载时跑其 cleanup

useRouteKeepAliveEffect

开启 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 入口

🔗 相关

About

受 Vue Router 启发的 React 路由库 · Vue 风格守卫 + Koa 风格中间件 + 组件级 keep-alive/LRU 缓存 + 全局布局 | Vue Router-inspired React routing — Vue-style guards, Koa-style middleware, component keep-alive/LRU cache & global layouts

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages