动态路由

在实际应用中,很多路由的路径结构是相似的,只有某些部分不同。例如用户详情页 /user/123/user/456,它们共享相同的页面结构,只是用户 ID 不同。动态路由允许你在路径中定义参数,匹配一类路径而非固定路径。

动态参数

使用 {param} 语法定义必填的动态参数:

ts
const routes = defineRoutes({ path: '/user/{id}', component: UserProfile })

上述配置会匹配 /user/123/user/abc 等路径,其中 {id} 部分会被提取为参数。

在组件中通过 useRoute() 获取参数:

tsx
import { useRoute } from 'vitarx-router'

function UserProfile() {
  const route = useRoute()
  return <div>用户 ID:{route.params.id}</div>
}

访问 /user/123 时,route.params.id 的值为 "123"

INFO

动态参数的值始终是字符串类型。如果需要数字类型,请自行转换:Number(route.params.id)

可选参数

使用 {param?} 语法定义可选参数,带有 ? 后缀的参数可以省略:

ts
const routes = defineRoutes({ path: '/user/{id?}', component: UserProfile })

上述配置会同时匹配:

  • /user —— params.id 不存在
  • /user/123 —— params.id"123"

WARNING

必填参数不能跟在可选参数后面。以下写法是无效的:

ts
// ❌ 错误:必填参数 {name} 跟在可选参数 {id?} 后面
{ path: '/user/{id?}/{name}', component: UserProfile }

这是因为如果 id 被省略,name 的位置会变得不确定,导致匹配歧义。

参数约束

默认情况下,动态参数匹配 [^/]+(即除 / 外的任意字符)。你可以通过 pattern 字段为参数指定正则约束:

ts
const routes = defineRoutes({
  path: '/user/{id}',
  component: UserProfile,
  // 约束 id 只能是数字
  pattern: { id: /^d+$/ }
})

配置后:

  • /user/123 ✅ 匹配成功,params.id"123"
  • /user/abc ❌ 匹配失败,因为 abc 不满足 ^\d+$

INFO

子路由会继承父路由的 pattern 配置。如果子路由定义了同名参数的 pattern,则子路由的约束会覆盖父路由的约束。

多个参数

一条路由可以包含多个动态参数段:

ts
const routes = defineRoutes({ path: '/post/{category}/{id}', component: PostDetail })

访问 /post/tech/42 时:

ts
route.params.category // "tech"
route.params.id // "42"

多个参数也可以与可选参数组合使用:

ts
const routes = defineRoutes({ path: '/archive/{year}/{month?}', component: Archive })
  • /archive/2024 —— params.year"2024"params.month 不存在
  • /archive/2024/06 —— params.year"2024"params.month"06"

完整示例

tsx
import { createRouter, defineRoutes, RouterView, useRoute } from 'vitarx-router'
import { createApp } from 'vitarx'

function UserProfile() {
  const route = useRoute()
  return (
    <div>
      <h2>用户详情</h2>
      <p>用户 ID:{route.params.id}</p>
    </div>
  )
}

function PostDetail() {
  const route = useRoute()
  return (
    <div>
      <h2>文章详情</h2>
      <p>分类:{route.params.category}</p>
      <p>文章 ID:{route.params.id}</p>
    </div>
  )
}

function Archive() {
  const route = useRoute()
  return (
    <div>
      <h2>归档</h2>
      <p>年份:{route.params.year}</p>
      {route.params.month && <p>月份:{route.params.month}</p>}
    </div>
  )
}

const routes = defineRoutes(
  { path: '/user/{id}', component: UserProfile, pattern: { id: /^d+$/ } },
  { path: '/post/{category}/{id}', component: PostDetail },
  { path: '/archive/{year}/{month?}', component: Archive }
)

const router = createRouter({ routes })

function App() {
  return <RouterView />
}

createApp(App).use(router).mount('#app')