动态路由允许你创建可以匹配多个 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}
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}
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}
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}
1// src/routes/user/[id]/loading.tsx
2export default function UserLoading() {
3 return (
4 <div>
5 <div className="skeleton">加载中...</div>
6 </div>
7 )
8}