动态路由

动态路由允许你创建可以匹配多个 URL 路径的路由,通过参数来处理不同的内容。

基础动态路由

使用方括号 [] 创建动态路由段:

src/routes/user/[id]/page.tsx → /user/123, /user/456 src/routes/post/[slug]/page.tsx → /post/hello-world, /post/my-story

获取路由参数

1// src/routes/user/[id]/page.tsx
2import { useParams } from 'react-router-dom'
3
4export default function UserPage() {
5  const { id } = useParams()
6  
7  return (
8    <div>
9      <h1>用户详情</h1>
10      <p>用户 ID: {id}</p>
11    </div>
12  )
13}

多层动态路由

src/routes/blog/[category]/[slug]/page.tsx → /blog/tech/react-hooks
1// src/routes/blog/[category]/[slug]/page.tsx
2import { useParams } from 'react-router-dom'
3
4export default function BlogPostPage() {
5  const { category, slug } = useParams()
6  
7  return (
8    <div>
9      <h1>博客文章</h1>
10      <p>分类: {category}</p>
11      <p>文章: {slug}</p>
12    </div>
13  )
14}

可选参数

使用双方括号 [[]] 创建可选参数:

src/routes/shop/[[category]]/page.tsx → /shop 和 /shop/electronics
1// src/routes/shop/[[category]]/page.tsx
2import { useParams } from 'react-router-dom'
3
4export default function ShopPage() {
5  const { category } = useParams()
6  
7  return (
8    <div>
9      <h1>商店</h1>
10      {category ? (
11        <p>分类: {category}</p>
12      ) : (
13        <p>所有商品</p>
14      )}
15    </div>
16  )
17}

捕获所有路由

使用 [...param] 捕获所有剩余路径:

src/routes/docs/[...slug]/page.tsx → /docs/guide/getting-started
1// src/routes/docs/[...slug]/page.tsx
2import { useParams } from 'react-router-dom'
3
4export default function DocsPage() {
5  const { slug } = useParams()
6  const path = Array.isArray(slug) ? slug.join('/') : slug
7  
8  return (
9    <div>
10      <h1>文档</h1>
11      <p>路径: {path}</p>
12    </div>
13  )
14}

数据加载

使用 loader 预加载数据

1// src/routes/user/[id]/page.tsx
2import { useLoaderData } from 'react-router-dom'
3
4export async function loader({ params }) {
5  const user = await fetchUser(params.id)
6  if (!user) {
7    throw new Response('User not found', { status: 404 })
8  }
9  return { user }
10}
11
12export default function UserPage() {
13  const { user } = useLoaderData()
14  
15  return (
16    <div>
17      <h1>{user.name}</h1>
18      <p>{user.email}</p>
19    </div>
20  )
21}

类型安全

配合 TypeScript 使用类型安全的参数:

1// src/routes/user/[id]/page.tsx
2import { useParams } from 'react-router-dom'
3
4interface UserParams {
5  id: string
6}
7
8export default function UserPage() {
9  const { id } = useParams<UserParams>()
10  
11  return (
12    <div>
13      <h1>用户 {id}</h1>
14    </div>
15  )
16}

最佳实践

1. Loader 参数验证

1export async function loader({ params }) {
2  const { id } = params
3  
4  // 验证参数格式
5  if (!/^\d+$/.test(id)) {
6    throw new Response('Invalid user ID', { status: 400 })
7  }
8  
9  const user = await fetchUser(id)
10  return { user }
11}

2. Error 错误处理

1// src/routes/user/[id]/error.tsx
2import { useRouteError } from 'react-router-dom'
3
4export default function UserError() {
5  const error = useRouteError()
6  
7  if (error.status === 404) {
8    return <div>用户不存在</div>
9  }
10  
11  return <div>加载用户信息时出错</div>
12}

3. Loading 加载状态

1// src/routes/user/[id]/loading.tsx
2export default function UserLoading() {
3  return (
4    <div>
5      <div className="skeleton">加载中...</div>
6    </div>
7  )
8}