类型安全

@feoe/fs-router 提供完整的 TypeScript 支持,确保路由导航的类型安全。

自动类型生成

插件会自动分析你的路由结构,生成对应的类型定义:

1// 自动生成的类型文件 src/routes-type.ts
2export interface Routes {
3  '/': {}
4  '/about': {}
5  '/user': {}
6  '/user/:id': { id: string }
7  '/blog/:category/:slug': { category: string; slug: string }
8}

类型安全的导航

1import { Link } from 'react-router-dom'
2
3// ✅ 类型安全的链接
4<Link to="/user/123">用户详情</Link>
5<Link to="/blog/tech/react-hooks">博客文章</Link>
6
7// ❌ TypeScript 会报错
8<Link to="/invalid-route">无效路由</Link>

编程式导航

1import { useNavigate } from 'react-router-dom'
2
3function MyComponent() {
4  const navigate = useNavigate()
5  
6  // ✅ 类型安全的导航
7  const goToUser = (id: string) => {
8    navigate(`/user/${id}`)
9  }
10  
11  // ✅ 带参数的导航
12  const goToBlog = (category: string, slug: string) => {
13    navigate(`/blog/${category}/${slug}`)
14  }
15}

参数类型推断

useParams Hook

1import { useParams } from 'react-router-dom'
2
3// src/routes/user/[id]/page.tsx
4export default function UserPage() {
5  // TypeScript 自动推断 id 为 string 类型
6  const { id } = useParams()
7  
8  return <div>用户 ID: {id}</div>
9}

自定义参数类型

1// src/routes/user/[id]/page.tsx
2interface UserParams {
3  id: string
4}
5
6export default function UserPage() {
7  const { id } = useParams<UserParams>()
8  
9  return <div>用户 ID: {id}</div>
10}

Loader 数据类型

类型安全的数据加载

1// src/routes/user/[id]/page.tsx
2import { useLoaderData } from 'react-router-dom'
3
4interface User {
5  id: string
6  name: string
7  email: string
8}
9
10export async function loader({ params }): Promise<{ user: User }> {
11  const user = await fetchUser(params.id)
12  return { user }
13}
14
15export default function UserPage() {
16  // TypeScript 自动推断数据类型
17  const { user } = useLoaderData<typeof loader>()
18  
19  return (
20    <div>
21      <h1>{user.name}</h1>
22      <p>{user.email}</p>
23    </div>
24  )
25}

配置类型定义

插件配置

1// vite.config.ts
2import { FileBasedRouterVite } from '@feoe/fs-router/vite'
3
4export default defineConfig({
5  plugins: [
6    FileBasedRouterVite({
7      routesDirectory: 'src/routes',
8      generatedRoutesPath: 'src/routes.tsx',
9      // 启用类型生成
10      enableGeneration: true,
11      typeGenerateOptions: {
12        routesTypeFile: 'src/routes-type.ts',
13        // 自定义类型生成选项
14        generateRouteParams: true,
15        generateLoaderTypes: true,
16        routesDirectories: []
17      }
18    })
19  ]
20})

高级类型特性

路由守卫类型

1interface RouteGuard<T = any> {
2  canActivate: (params: T) => boolean | Promise<boolean>
3}
4
5// src/routes/admin/[id]/page.tsx
6export const guard: RouteGuard<{ id: string }> = {
7  canActivate: ({ id }) => {
8    return checkAdminPermission(id)
9  }
10}

元数据类型

1interface RouteMeta {
2  title?: string
3  description?: string
4  requiresAuth?: boolean
5}
6
7// src/routes/user/[id]/page.tsx
8export const meta: RouteMeta = {
9  title: '用户详情',
10  description: '查看用户详细信息',
11  requiresAuth: true
12}

类型检查配置

tsconfig.json

1{
2  "compilerOptions": {
3    "strict": true,
4    "noImplicitAny": true,
5    "strictNullChecks": true,
6    "paths": {
7      "@/*": ["./src/*"],
8      "@/routes": ["./src/routes.tsx"],
9      "@/routes-type": ["./src/routes-type.ts"]
10    }
11  },
12  "include": [
13    "src/**/*",
14    "src/routes-type.ts"
15  ]
16}

最佳实践

1. 启用严格模式

1// tsconfig.json
2{
3  "compilerOptions": {
4    "strict": true,
5    "noImplicitReturns": true,
6    "noFallthroughCasesInSwitch": true
7  }
8}

2. 使用接口定义

1// 定义清晰的接口
2interface BlogPost {
3  id: string
4  title: string
5  content: string
6  author: User
7  createdAt: Date
8}
9
10export async function loader({ params }) {
11  const post: BlogPost = await fetchBlogPost(params.slug)
12  return { post }
13}

3. 错误类型处理

1import { isRouteErrorResponse } from 'react-router-dom'
2
3export default function ErrorBoundary() {
4  const error = useRouteError()
5  
6  if (isRouteErrorResponse(error)) {
7    return (
8      <div>
9        <h1>{error.status} {error.statusText}</h1>
10        <p>{error.data}</p>
11      </div>
12    )
13  }
14  
15  return <div>未知错误</div>
16}