@tangmu1121/rvue-router
v0.3.4
Published
Vue Router-style routing for React — createRouter, useRoute, useRouter, file-based routing, navigation guards, and more.
Maintainers
Readme
@tangmu1121/rvue-router
为 React 提供 Vue Router 风格的路由方案。如果你喜欢 Vue Router 的 API 设计,这个库将带给你完全一致的开发体验。
目录
特性
createRouter工厂函数,插件化配置风格useRouter/useRoutehooks,完全响应式<RouterView>支持嵌套布局(多层路由出口)<RouterLink>支持activeClass、exactActiveClass、disabled、aria-currentuseLinkhook,用于构建自定义导航组件- 导航守卫:全局
beforeEach、afterEach,路由级beforeEnter - 组件级守卫:
useBeforeRouteLeave、useBeforeRouteUpdate router.addRoute/removeRoute动态添加/删除路由router.resolve— 仅解析路由,不执行跳转router.isReady()— 等待初始导航完成的 PromiseuseIsNavigating— 全局导航加载状态 hookscrollBehavior— 自定义滚动恢复逻辑- 路由
alias别名支持 - 命名视图(
components: { default, sidebar }) - 三种历史模式:
createWebHistory/createHashHistory/createMemoryHistory - 完整的 TypeScript 类型支持
- 文件系统路由:Vite 插件,按目录结构自动生成路由配置
- 路由缓存(Keep-Alive):include / exclude / max,
useKeepAliveActive实现激活/未激活
安装
npm install @tangmu1121/rvue-routerpnpm add @tangmu1121/rvue-routeryarn add @tangmu1121/rvue-router快速上手
第一步:创建路由器
// src/router/index.tsx
import { lazy } from 'react'
import { createRouter, createWebHistory } from '@tangmu1121/rvue-router'
const Home = lazy(() => import('@/pages/Home'))
const About = lazy(() => import('@/pages/About'))
const User = lazy(() => import('@/pages/User'))
const NotFound = lazy(() => import('@/pages/NotFound'))
export const router = createRouter({
history: createWebHistory(),
// 滚动行为:前进/后退时恢复原位置,新导航时回到顶部
scrollBehavior(_to, _from, savedPosition) {
return savedPosition ?? { top: 0 }
},
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', name: 'home', component: Home, meta: { title: '首页' } },
{ path: '/about', name: 'about', component: About, meta: { title: '关于' } },
{
path: '/users',
children: [
{ path: '', name: 'user-list', component: User },
{ path: ':id', name: 'user-detail', component: User },
],
},
{ path: '*', component: NotFound },
],
})
// 注册全局前置守卫
router.beforeEach((to, _from, next) => {
if (to.meta.requiresAuth && !localStorage.getItem('token')) {
next('/login')
} else {
next()
}
})
// 注册全局后置守卫
router.afterEach((to) => {
document.title = String(to.meta.title ?? 'App')
})第二步:注入路由上下文
// src/main.tsx
import { createRoot } from 'react-dom/client'
import App from './App'
import { router } from './router'
const { RouterProvider } = router
createRoot(document.getElementById('root')!).render(
<RouterProvider>
<App />
</RouterProvider>
)第三步:渲染路由出口
// src/App.tsx
import { RouterView, RouterLink } from '@tangmu1121/rvue-router'
export default function App() {
return (
<div>
<nav>
<RouterLink to="/home" activeClass="active" exactActiveClass="active-exact">首页</RouterLink>
<RouterLink to="/about" activeClass="active">关于</RouterLink>
</nav>
{/* 路由组件在此渲染 */}
<RouterView />
</div>
)
}路由配置
基础路由
routes: [
{ path: '/home', component: Home },
{ path: '/about', component: About },
]动态路由参数
使用 :参数名 定义动态段,在组件内通过 useRoute().params 获取:
routes: [
{ path: '/users/:id', component: UserDetail },
// 多段参数
{ path: '/posts/:year/:month/:slug', component: Post },
]// 在组件中读取
function UserDetail() {
const route = useRoute()
const { id } = route.params // string 类型
// ...
}嵌套路由
子路由的 path 相对于父路由,使用空字符串 '' 表示默认子路由:
routes: [
{
path: '/users',
component: UsersLayout, // 父布局组件,内部需要 <RouterView />
children: [
{ path: '', component: UserList }, // 匹配 /users
{ path: ':id', component: UserDetail }, // 匹配 /users/123
{
path: ':id/posts',
component: UserPosts,
children: [
{ path: ':postId', component: UserPost }, // 匹配 /users/123/posts/456
]
}
],
},
]父布局组件中需放置 <RouterView /> 以渲染子路由:
function UsersLayout() {
return (
<div>
<aside>用户侧边栏</aside>
<main>
<RouterView /> {/* 子路由渲染在这里 */}
</main>
</div>
)
}命名路由
为路由添加 name 字段后,可以通过名称进行导航,避免硬编码路径:
routes: [
{ path: '/users/:id', name: 'user-detail', component: UserDetail },
]// 通过名称导航(name 导航目前需要配合 path 使用,推荐直接用 path)
router.push({ path: '/users/123' })
// RouterLink 同样支持对象形式
<RouterLink to={{ path: '/users/123', query: { tab: 'profile' } }}>
用户详情
</RouterLink>路由别名
alias 允许用多个路径访问同一个路由组件:
routes: [
{
path: '/users/:id',
alias: '/u/:id', // 单个别名
// alias: ['/u/:id', '/member/:id'], // 多个别名
component: UserDetail,
},
]重定向
routes: [
// 字符串重定向
{ path: '/', redirect: '/home' },
// 对象形式
{ path: '/old-path', redirect: { path: '/new-path' } },
// 带查询参数
{ path: '/legacy', redirect: { path: '/modern', query: { version: '2' } } },
]通配符(404 页面)
使用 * 匹配所有未匹配的路径,通常放在路由列表末尾:
routes: [
// ... 其他路由
{ path: '*', component: NotFound },
]路由元信息
通过 meta 字段挂载任意数据,在守卫和组件中均可访问:
routes: [
{
path: '/dashboard',
component: Dashboard,
meta: {
requiresAuth: true,
title: '控制台',
roles: ['admin', 'editor'],
},
},
]// 在守卫中使用
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth) {
// 鉴权逻辑
}
next()
})
// 在组件中使用
function Dashboard() {
const route = useRoute()
console.log(route.meta.title) // '控制台'
}历史模式
import {
createWebHistory,
createHashHistory,
createMemoryHistory,
} from '@tangmu1121/rvue-router'| 模式 | URL 形式 | 说明 |
|------|----------|------|
| createWebHistory() | /path/to/page | 基于 HTML5 History API,需服务器配置回退到 index.html |
| createHashHistory() | /#/path/to/page | Hash 模式,无需服务器配置,兼容性好 |
| createMemoryHistory() | 内存中 | 不依赖浏览器 API,适合 SSR 或单元测试 |
// Web History(推荐,需要服务器支持)
createRouter({ history: createWebHistory(), routes })
// Hash History(不需要服务器配置)
createRouter({ history: createHashHistory(), routes })
// Memory History(SSR / 测试环境)
createRouter({ history: createMemoryHistory('/initial-path'), routes })
// 也可以直接用字符串简写
createRouter({ history: 'browser', routes }) // createWebHistory
createRouter({ history: 'hash', routes }) // createHashHistory
createRouter({ history: 'memory', routes }) // createMemoryHistoryNginx 服务器配置示例(Web History 模式)
location / { try_files $uri $uri/ /index.html; }
导航守卫
导航守卫按以下顺序依次执行:
- 组件
useBeforeRouteLeave(离开旧组件) - 全局
beforeEach - 组件
useBeforeRouteUpdate(参数更新但组件复用时) - 路由级
beforeEnter(进入新路由时)
每个守卫都接收 (to, from, next) 三个参数:
next()— 确认导航next(false)— 取消导航,停留在当前页面next('/path')— 重定向到新路径
全局前置守卫
const removeGuard = router.beforeEach((to, from, next) => {
// 鉴权示例
if (to.meta.requiresAuth && !isLoggedIn()) {
next('/login')
} else {
next()
}
})
// 调用返回值可以移除该守卫
removeGuard()全局后置守卫
afterEach 在导航完成后执行,不需要调用 next:
router.afterEach((to, from) => {
// 更新页面标题
document.title = String(to.meta.title ?? 'My App')
// 上报埋点
analytics.track('page_view', { path: to.fullPath })
})路由级别守卫
在路由配置中使用 beforeEnter,仅在进入该路由时执行:
routes: [
{
path: '/admin',
component: AdminPage,
beforeEnter: (to, from, next) => {
if (!hasAdminRole()) {
next('/403')
} else {
next()
}
},
},
{
path: '/profile',
component: ProfilePage,
// 也可以是守卫数组,按顺序执行
beforeEnter: [checkAuth, checkEmailVerified],
},
]组件级别守卫
在组件内部使用 hooks 注册守卫:
import { useBeforeRouteLeave, useBeforeRouteUpdate } from '@tangmu1121/rvue-router'
function EditForm() {
const [isDirty, setIsDirty] = useState(false)
// 离开时确认
useBeforeRouteLeave((to, from, next) => {
if (isDirty && !confirm('有未保存的更改,确认离开?')) {
next(false) // 取消导航
} else {
next()
}
})
// 路由参数变化时(组件复用场景,如从 /users/1 跳到 /users/2)
useBeforeRouteUpdate((to, from, next) => {
// 用新参数重新加载数据
fetchUserData(to.params.id)
next()
})
// ...
}错误处理
router.onError((error) => {
console.error('路由导航出错:', error)
})API 参考
createRouter
createRouter(options: RouterOptions): RouterInstance & { RouterProvider }| 选项 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| routes | RouteRecordRaw[] | — | 路由配置数组(必填) |
| history | RouterHistory \| 'browser' \| 'hash' \| 'memory' | 'browser' | 历史模式 |
| scrollBehavior | ScrollBehaviorHandler | — | 滚动行为控制函数 |
router 实例方法
导航方法
// 推入新历史记录
router.push('/path')
router.push({ path: '/users', query: { page: '2' } })
router.push({ path: '/users/123', hash: '#comments' })
// 替换当前历史记录(不新增历史条目)
router.replace('/path')
router.replace({ path: '/new-page' })
// 历史栈操作
router.back() // 等同 history.go(-1)
router.forward() // 等同 history.go(1)
router.go(-2) // 后退两步
router.go(3) // 前进三步路由信息
// 解析路由(不执行跳转)
const resolved = router.resolve('/users/123')
// → RouteLocationNormalized { path, fullPath, params, query, hash, meta, matched }
// 等待初始导航完成
await router.isReady()
// 获取当前路由
const current = router.currentRoute
// 检查是否有某个命名路由
router.hasRoute('user-detail') // boolean
// 获取所有路由配置
router.getRoutes() // RouteRecordRaw[]动态路由
// 添加顶层路由
router.addRoute({ path: '/new-page', name: 'new-page', component: NewPage })
// 添加子路由(指定父路由名称)
router.addRoute({ path: 'settings', name: 'user-settings', component: Settings }, 'user-detail')
// 删除路由(通过名称)
router.removeRoute('new-page')守卫注册
// 所有注册方法都返回取消函数
const removeBeforeEach = router.beforeEach((to, from, next) => { next() })
const removeAfterEach = router.afterEach((to, from) => {})
const removeOnError = router.onError((err) => {})
// 取消注册
removeBeforeEach()useRouter
获取路由器实例,等同 Vue Router 的 useRouter():
import { useRouter } from '@tangmu1121/rvue-router'
function MyComponent() {
const router = useRouter()
const handleClick = () => {
router.push('/dashboard')
}
return <button onClick={handleClick}>跳转</button>
}useRoute
获取当前路由对象,响应式,路由变化时自动触发重渲染。等同 Vue Router 的 useRoute():
import { useRoute } from '@tangmu1121/rvue-router'
function UserDetail() {
const route = useRoute()
return (
<div>
<p>路径:{route.path}</p>
<p>完整路径:{route.fullPath}</p>
<p>路由名称:{route.name}</p>
<p>参数 id:{route.params.id}</p>
<p>查询参数 page:{route.query.page}</p>
<p>Hash:{route.hash}</p>
</div>
)
}RouteLocationNormalized 结构:
| 字段 | 类型 | 说明 |
|------|------|------|
| path | string | 当前路径(不含 query 和 hash) |
| fullPath | string | 完整路径(含 query 和 hash) |
| name | string \| undefined | 路由名称 |
| params | Record<string, string> | 动态路由参数 |
| query | Record<string, string> | URL 查询参数 |
| hash | string | URL hash(含 # 前缀) |
| meta | RouteMeta | 路由元信息 |
| matched | RouteRecordRaw[] | 从根到当前路由的匹配链 |
| redirectedFrom | RouteLocationNormalized \| undefined | 若由重定向触发,记录原始目标 |
useIsNavigating
返回当前是否有导航守卫正在执行,可用于全局加载指示器:
import { useIsNavigating } from '@tangmu1121/rvue-router'
function GlobalLoader() {
const isNavigating = useIsNavigating()
if (!isNavigating) return null
return (
<div className="global-loader">
<Spinner />
</div>
)
}useLink
暴露 RouterLink 的核心逻辑,用于构建完全自定义的导航组件:
import { useLink } from '@tangmu1121/rvue-router'
function MyNavLink({ to, children }: { to: string; children: React.ReactNode }) {
const { href, isActive, isExactActive, navigate } = useLink({ to })
return (
<a
href={href}
onClick={navigate}
className={[
'nav-link',
isActive ? 'nav-link--active' : '',
isExactActive ? 'nav-link--exact-active' : '',
].filter(Boolean).join(' ')}
>
{children}
</a>
)
}useLink 参数:
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| to | string \| RouteLocationRaw | — | 目标路由(必填) |
| replace | boolean | false | 是否使用 router.replace |
useLink 返回值:
| 字段 | 类型 | 说明 |
|------|------|------|
| href | string | 解析后的 href 字符串,用于 <a href> |
| isActive | boolean | 当前路径以 to 开头时为 true(前缀匹配) |
| isExactActive | boolean | 当前路径与 to 完全匹配时为 true |
| navigate | (e?: MouseEvent) => void | 执行导航,传入 MouseEvent 时自动处理修饰键 |
RouterLink 组件
声明式导航组件,对应 Vue Router 的 <RouterLink>。
import { RouterLink } from '@tangmu1121/rvue-router'
// 基础用法
<RouterLink to="/home">首页</RouterLink>
// 带激活样式
<RouterLink to="/home" activeClass="active" exactActiveClass="active-exact">
首页
</RouterLink>
// 对象形式(支持 query 和 hash)
<RouterLink to={{ path: '/users', query: { page: '2' }, hash: '#list' }}>
用户列表第 2 页
</RouterLink>
// 替换历史记录(不新增历史条目)
<RouterLink to="/login" replace>登录</RouterLink>
// 禁用状态(渲染为 <a> 但阻止跳转)
<RouterLink to="/admin" disabled>管理员(无权限)</RouterLink>Props 说明:
| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| to | string \| RouteLocationRaw | — | 目标路由(必填) |
| replace | boolean | false | 使用 router.replace 而非 router.push |
| activeClass | string | '' | 路径前缀匹配时追加的 CSS class |
| exactActiveClass | string | '' | 路径精确匹配时追加的 CSS class |
| disabled | boolean | false | 禁用导航(追加 aria-disabled) |
RouterLink支持所有原生<a>属性(除href外),如className、style、onClick等。精确匹配时会自动添加
aria-current="page"属性,满足无障碍标准。按住 Ctrl / Meta / Shift / Alt 点击时,会走浏览器默认行为(如在新标签页打开),不触发路由导航。
RouterView 组件
路由出口组件,渲染当前路由匹配的组件:
import { RouterView } from '@tangmu1121/rvue-router'
// 基础用法(渲染默认视图)
<RouterView />
// 命名视图
<RouterView name="sidebar" />
// 自定义加载占位内容
<RouterView fallback={<div>页面加载中...</div>} />Props 说明:
| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| name | string | 'default' | 命名视图名称,对应路由配置中 components 的 key |
| fallback | ReactNode | null(不渲染) | Suspense 加载占位内容,不传则静默等待 |
| keepAlive | boolean \| KeepAliveConfig | — | 是否缓存路由组件(等同 Vue keep-alive),见下方「路由缓存」 |
| transition | string \| TransitionConfig | — | 路由切换转场动画 |
RouterView内置<Suspense>支持,配合React.lazy()懒加载组件时无需手动包裹。
路由缓存(Keep-Alive)
与 Vue 的 <keep-alive> 等价:离开页面时不销毁组件,再次进入时保留状态(表单、滚动位置、定时器等)。
基础用法
// 全部路由都缓存
<RouterView keepAlive />
// 按配置缓存
<RouterView
keepAlive={{
include: ['user-detail', 'settings'], // 只缓存这些 name
exclude: /^Admin/, // 不缓存 name 匹配该正则的
max: 10, // 最多缓存 10 个,超出按 LRU 淘汰
}}
/>include / exclude
- include:只有匹配到的路由会被缓存;不传则与「是否缓存」无关(由 exclude 决定)。
- exclude:匹配到的路由不会被缓存,优先级高于 include。
匹配规则(对 route.name 与 route.path 生效):
| 类型 | 说明 |
|------|------|
| string | 与 route.name 或 route.path 精确相等 |
| RegExp | 对 route.name、route.path 执行 test() |
| string[] | 任一元素匹配即命中 |
// 只缓存列表和详情
<RouterView keepAlive={{ include: ['user-list', 'user-detail'] }} />
// 不缓存管理后台
<RouterView keepAlive={{ exclude: ['admin', 'admin-login'] }} />
// 正则:只缓存 name 以 Page 结尾的
<RouterView keepAlive={{ include: /Page$/ }} />max(LRU 淘汰)
设置最多缓存的组件实例数,超出时按访问顺序淘汰最久未用的:
<RouterView keepAlive={{ max: 10 }} />激活 / 未激活:useKeepAliveActive
缓存中的页面在「被展示」和「在后台」时可用 useKeepAliveActive() 区分,用于暂停轮询、视频等:
import { useKeepAliveActive } from '@tangmu1121/rvue-router'
function DataList() {
const isActive = useKeepAliveActive()
useEffect(() => {
if (!isActive) return
const id = setInterval(fetchList, 5000)
return () => clearInterval(id)
}, [isActive])
return <div>...</div>
}- true:当前是展示中的页面,或未使用 Keep-Alive。
- false:当前在缓存中但被隐藏(相当于 Vue 的 deactivated)。
与 transition 的关系
开启 keepAlive 时,不会同时应用 transition(避免两层动画逻辑)。若需要「缓存 + 转场」,可在外层自行包一层动画组件。
命名视图
当一个路由需要同时渲染多个并列组件时,可使用命名视图:
// 路由配置
routes: [
{
path: '/layout',
components: {
default: MainContent, // 对应 <RouterView />
sidebar: SidebarPanel, // 对应 <RouterView name="sidebar" />
header: PageHeader, // 对应 <RouterView name="header" />
},
},
]// 布局组件中同时渲染多个出口
function AppLayout() {
return (
<div className="app-layout">
<header>
<RouterView name="header" />
</header>
<aside>
<RouterView name="sidebar" />
</aside>
<main>
<RouterView /> {/* 等同 name="default" */}
</main>
</div>
)
}动态路由
在运行时动态添加或删除路由,适用于权限路由、插件化路由等场景:
// 在用户登录后,根据权限动态添加路由
async function setupRoutes(userRoles: string[]) {
await router.isReady()
if (userRoles.includes('admin')) {
router.addRoute({
path: '/admin',
name: 'admin',
component: AdminDashboard,
meta: { title: '管理后台' },
})
}
// 添加到指定父路由下
router.addRoute(
{ path: 'reports', name: 'admin-reports', component: Reports },
'admin' // 父路由名称
)
}
// 注销后清理路由
function cleanup() {
router.removeRoute('admin')
router.removeRoute('admin-reports')
}
// 检查路由是否存在
if (router.hasRoute('admin')) {
console.log('管理员路由已注册')
}路由转场动画
<RouterView> 内置了与 Vue <Transition> 完全一致的 CSS 类名约定,无需额外依赖即可实现路由切换动画。
基础用法
给 <RouterView> 传入 transition prop:
// 字符串形式:使用 CSS 类名前缀
<RouterView transition="fade" />
// 对象形式:完整配置
<RouterView transition={{ name: 'slide', duration: 300 }} />
// 首次渲染也执行动画(appear)
<RouterView transition={{ name: 'fade', appear: true }} />然后在全局 CSS 中定义对应的过渡样式:
/* ─── Fade 淡入淡出 ─────────────────────────────── */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* ─── Slide 水平滑动 ────────────────────────────── */
.slide-enter-active,
.slide-leave-active {
transition: all 0.35s ease;
}
.slide-enter-from {
opacity: 0;
transform: translateX(30px);
}
.slide-leave-to {
opacity: 0;
transform: translateX(-30px);
}
/* ─── Zoom 缩放 ─────────────────────────────────── */
.zoom-enter-active,
.zoom-leave-active {
transition: all 0.25s ease;
}
.zoom-enter-from,
.zoom-leave-to {
opacity: 0;
transform: scale(0.95);
}CSS 类名说明
每次路由切换时,插件按以下顺序在 <RouterView> 的外层 div 上添加/移除 CSS 类(out-in 模式):
旧组件离开阶段:
| 时机 | 添加的类 | 移除的类 |
|------|----------|----------|
| 离开开始 | name-leave-from、name-leave-active | — |
| 下一帧 | name-leave-to | name-leave-from |
| 动画结束 | — | name-leave-active、name-leave-to |
新组件进入阶段(旧组件离开完成后):
| 时机 | 添加的类 | 移除的类 |
|------|----------|----------|
| 进入开始 | name-enter-from、name-enter-active | — |
| 下一帧 | name-enter-to | name-enter-from |
| 动画结束 | — | name-enter-active、name-enter-to |
使用 Tailwind CSS
通过完全自定义类名,可以直接使用 Tailwind 的原子类:
<RouterView
transition={{
enterFromClass: 'opacity-0 translate-y-2',
enterActiveClass: 'transition-all duration-300 ease-out',
enterToClass: 'opacity-100 translate-y-0',
leaveFromClass: 'opacity-100 translate-y-0',
leaveActiveClass: 'transition-all duration-200 ease-in',
leaveToClass: 'opacity-0 -translate-y-2',
}}
/>使用 CSS Modules
import styles from './transitions.module.css'
<RouterView
transition={{
enterFromClass: styles.enterFrom,
enterActiveClass: styles.enterActive,
enterToClass: styles.enterTo,
leaveFromClass: styles.leaveFrom,
leaveActiveClass: styles.leaveActive,
leaveToClass: styles.leaveTo,
}}
/>transition 选项说明
| 选项 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| name | string | 'v' | CSS 类名前缀 |
| enterFromClass | string | '${name}-enter-from' | 自定义进入起始类 |
| enterActiveClass | string | '${name}-enter-active' | 自定义进入激活类 |
| enterToClass | string | '${name}-enter-to' | 自定义进入结束类 |
| leaveFromClass | string | '${name}-leave-from' | 自定义离开起始类 |
| leaveActiveClass | string | '${name}-leave-active' | 自定义离开激活类 |
| leaveToClass | string | '${name}-leave-to' | 自定义离开结束类 |
| mode | 'out-in' | 'out-in' | 过渡模式(旧出新入) |
| duration | number \| { enter: number; leave: number } | 自动检测 | 显式时长(ms) |
| appear | boolean | false | 首次渲染时执行进入动画 |
过渡时长检测: 未指定
duration时,自动从元素的transition-duration和animation-duration计算最大时长(含transition-delay)。动画期间的快速导航: 若动画进行中触发新的路由跳转,新跳转会被缓存,等当前动画结束后立即执行。
懒加载组件: 与
React.lazy()完全兼容,进入动画会在组件加载完毕后执行(Suspense 完成时)。
不同路由使用不同动画
通过读取路由 meta 动态决定动画:
import { useRoute } from '@tangmu1121/rvue-router'
function AnimatedRouterView() {
const route = useRoute()
const transition = route.meta.transition ?? 'fade'
return <RouterView transition={transition} />
}// 路由配置
routes: [
{ path: '/home', component: Home, meta: { transition: 'fade' } },
{ path: '/dashboard', component: Dashboard, meta: { transition: 'slide' } },
{ path: '/settings', component: Settings, meta: { transition: 'zoom' } },
]滚动行为
scrollBehavior 函数在每次路由导航后调用,用于控制页面滚动位置:
createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
// 浏览器前进/后退:恢复之前的滚动位置
if (savedPosition) {
return savedPosition
}
// 带 hash 时滚动到对应锚点
if (to.hash) {
return {
el: to.hash, // CSS 选择器或 Element
behavior: 'smooth', // 平滑滚动
}
}
// 默认回到顶部
return { top: 0, left: 0 }
},
})scrollBehavior 返回值类型:
| 返回值 | 说明 |
|--------|------|
| { top?: number, left?: number, behavior?: ScrollBehavior } | 滚动到指定坐标 |
| { el: string \| Element, top?: number, left?: number, behavior?: ScrollBehavior } | 滚动到指定元素 |
| false \| void | 不做任何滚动 |
scrollBehavior支持返回Promise,可以在异步操作后再决定滚动位置。
TypeScript 类型
所有类型均从包中导出,可直接使用:
import type {
// 路由配置
RouteRecordRaw,
RouteMeta,
RouteConfig, // *.route.ts 同级配置文件的导出类型
// 转场动画
TransitionConfig,
TransitionProp,
// 路由缓存
KeepAliveConfig,
// 路由位置
RouteLocationRaw,
RouteLocationNormalized,
// 守卫
NavigationGuard,
NavigationGuardNext,
AfterEachHook,
// 滚动行为
ScrollBehaviorHandler,
ScrollBehaviorResult,
ScrollPositionCoords,
ScrollPositionElement,
// 路由器
RouterInstance,
RouterOptions,
RouterHistory,
} from '@tangmu1121/rvue-router'
// 运行时辅助
import { defineRouteConfig } from '@tangmu1121/rvue-router'扩展路由元信息类型
通过 TypeScript 模块扩充可为 RouteMeta 添加类型安全的自定义字段:
// src/types/router.d.ts
import '@tangmu1121/rvue-router'
declare module '@tangmu1121/rvue-router' {
interface RouteMeta {
title?: string
requiresAuth?: boolean
roles?: string[]
layout?: 'default' | 'blank' | 'admin'
}
}文件系统路由(自动生成)
通过内置的 Vite 插件,按照文件目录结构自动生成路由配置,无需手写 routes 数组。创建页面文件即可完成路由注册。
快速接入
第一步:注册 Vite 插件
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { fileRouter } from '@tangmu1121/rvue-router/vite'
export default defineConfig({
plugins: [
react(),
fileRouter({ dir: 'src/pages' }), // 扫描 src/pages 目录
],
})第二步:配置 TypeScript 类型(可选)
// tsconfig.json
{
"compilerOptions": {
"types": ["@tangmu1121/rvue-router/client"]
}
}或在 src/env.d.ts 中添加一行:
/// <reference types="@tangmu1121/rvue-router/client" />第三步:在路由器中引入
// src/router/index.ts
import routes from 'virtual:rvue-routes'
import { createRouter, createWebHistory } from '@tangmu1121/rvue-router'
export const router = createRouter({
history: createWebHistory(),
routes, // 自动生成的路由配置
})
// 仍然可以注册守卫
router.beforeEach((to, from, next) => {
next()
})第四步:创建页面文件
只需在 src/pages 目录下创建文件,路由自动生成:
src/pages/
index.tsx → /
about.tsx → /about
users/
index.tsx → /users
[id].tsx → /users/:id文件命名约定
| 文件/目录 | 生成路由 path | 自动 name | 说明 |
|-----------|--------------|-----------|------|
| index.tsx | ''(子路由)或 /(根) | 'index' | 当前层级的索引页 |
| about.tsx | about / /about | 'about' | 静态路径段 |
| [id].tsx | :id | '...-id' | 动态路由参数 |
| [...all].tsx | * | '...-all' | 通配符(捕获所有未匹配路径) |
| 404.tsx | * | '404' | 通配符,用于 404 页面 |
| _layout.tsx | — | 取目录名 | 布局组件,将目录内路由变为嵌套子路由 |
| [page].route.ts | — | — | 同级路由配置,声明 meta / guards / name 覆盖 |
| _*.tsx | — | — | 下划线前缀文件均被忽略(除 _layout) |
| .hidden.tsx | — | — | 点前缀文件被忽略 |
动态目录名同样支持:
[id]/→:id,自动 name 中会展开为id。
目录结构示例
示例一:扁平路由(无布局文件)
src/pages/
index.tsx
about.tsx
users/
index.tsx
[id].tsx
[...404].tsx生成路由(含自动名称):
[
{ path: '/', name: 'index', component: lazy(() => import('./pages/index.tsx')) },
{ path: '/about', name: 'about', component: lazy(() => import('./pages/about.tsx')) },
{ path: '/users', name: 'users', component: lazy(() => import('./pages/users/index.tsx')) },
{ path: '/users/:id', name: 'users-id', component: lazy(() => import('./pages/users/[id].tsx')) },
{ path: '*', name: '404', component: lazy(() => import('./pages/[...404].tsx')) },
]示例二:带布局的嵌套路由
src/pages/
_layout.tsx ← 根布局(包裹所有页面)
index.tsx
about.tsx
users/
_layout.tsx ← /users 布局(包裹 /users/* 页面)
index.tsx
[id].tsx
[id]/
_layout.tsx ← /users/:id 布局
posts.tsx
settings.tsx
[...404].tsx生成路由(含自动名称):
[
{
path: '/',
component: lazy(() => import('./pages/_layout.tsx')), // 根布局
children: [
{ path: '', name: 'index', component: lazy(() => import('./pages/index.tsx')) },
{ path: 'about', name: 'about', component: lazy(() => import('./pages/about.tsx')) },
{
path: 'users',
name: 'users',
component: lazy(() => import('./pages/users/_layout.tsx')),
children: [
{ path: '', name: 'users', component: lazy(() => import('./pages/users/index.tsx')) },
{
path: ':id',
name: 'users-id',
component: lazy(() => import('./pages/users/[id]/_layout.tsx')),
children: [
{ path: 'posts', name: 'users-id-posts', component: lazy(() => import('./pages/users/[id]/posts.tsx')) },
{ path: 'settings', name: 'users-id-settings', component: lazy(() => import('./pages/users/[id]/settings.tsx')) },
],
},
],
},
{ path: '*', name: '404', component: lazy(() => import('./pages/[...404].tsx')) },
],
},
]布局组件内需要放置 <RouterView /> 来渲染子路由:
// src/pages/_layout.tsx
import { RouterView, RouterLink } from '@tangmu1121/rvue-router'
export default function RootLayout() {
return (
<div>
<nav>
<RouterLink to="/" exactActiveClass="active">首页</RouterLink>
<RouterLink to="/about" activeClass="active">关于</RouterLink>
<RouterLink to="/users" activeClass="active">用户</RouterLink>
</nav>
<main>
<RouterView /> {/* 子页面渲染在这里 */}
</main>
</div>
)
}自动路由名称
插件会根据文件路径自动生成路由 name,规则如下:
| 文件路径 | 自动生成 name |
|----------|--------------|
| pages/index.tsx | 'index' |
| pages/about.tsx | 'about' |
| pages/users/index.tsx | 'users'(去掉末尾的 index)|
| pages/users/[id].tsx | 'users-id' |
| pages/users/[id]/posts.tsx | 'users-id-posts' |
| pages/[...404].tsx | '404' |
自动名称可以直接在 router.push / RouterLink 中使用:
// 命名导航
router.push({ name: 'users-id', params: { id: '123' } })
// RouterLink 使用 name(通过 path 传入解析后的路径)
<RouterLink to="/users/123">用户详情</RouterLink>若需要覆盖自动生成的 name,可使用同级路由配置文件(见下节)。
同级路由配置文件(*.route.ts)
对于需要额外配置(meta、beforeEnter、自定义 name)的页面,
可在同目录下创建 [页面名].route.ts,插件会自动将其默认导出 spread 到路由对象中。
src/pages/
users/
[id].tsx ← 页面组件
[id].route.ts ← 该路由的额外配置(可选)
dashboard.tsx
dashboard.route.ts// src/pages/users/[id].route.ts
import { defineRouteConfig } from '@tangmu1121/rvue-router'
export default defineRouteConfig({
// 覆盖自动生成的 name
name: 'user-detail',
// 路由元信息
meta: {
requiresAuth: true,
title: '用户详情',
},
// 路由级前置守卫
beforeEnter: (to, from, next) => {
if (!to.params.id) {
next('/users')
} else {
next()
}
},
})// src/pages/dashboard.route.ts
import { defineRouteConfig } from '@tangmu1121/rvue-router'
export default defineRouteConfig({
name: 'dashboard',
meta: { requiresAuth: true, title: '控制台', roles: ['admin'] },
// 也可以传入守卫数组
beforeEnter: [checkAuth, checkRole],
})生成的路由对象(以 [id].tsx 为例):
{
path: '/users/:id',
name: 'users-id', // 先写自动生成的 name
component: lazy(() => import('...')),
...routeConfig, // spread 覆盖(name → 'user-detail',加入 meta 和 beforeEnter)
}
defineRouteConfig是一个纯透传的辅助函数,运行时开销为零,仅提供 TypeScript 类型检查。
配置文件中不能覆盖path、component、components、children,这些由插件统一管理。
混合使用(自动 + 手动路由)
自动生成的路由与手动添加的路由可以共存:
import autoRoutes from 'virtual:rvue-routes'
import { createRouter, createWebHistory } from '@tangmu1121/rvue-router'
const router = createRouter({
history: createWebHistory(),
routes: autoRoutes,
})
// 登录后动态添加权限路由
router.addRoute({
path: '/admin',
name: 'admin',
component: () => import('./pages/admin/index.tsx'),
})fileRouter 插件选项
fileRouter({
dir: 'src/pages', // 页面目录(相对于项目根目录)
extensions: ['.tsx', '.ts', '.jsx', '.js'], // 扫描的文件扩展名
})| 选项 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| dir | string | 'src/pages' | 页面目录路径,相对于 Vite 根目录 |
| extensions | string[] | ['.tsx', '.ts', '.jsx', '.js'] | 扫描的文件扩展名 |
HMR 热更新
插件自动监听 pages 目录的变化:
| 操作 | 触发行为 |
|------|----------|
| 新增页面文件 / 目录 | 重新生成路由配置,触发全页面刷新 |
| 删除页面文件 / 目录 | 重新生成路由配置,触发全页面刷新 |
| 修改 *.route.ts 内容 | 重新生成路由配置,触发全页面刷新(meta / guards 更新) |
| 修改页面组件内容 | Vite 标准 HMR,不触发路由重新生成 |
License
MIT
