学习目标
理解 Nuxt 动态路由的设计思想,而不是死记语法。
学完本章,你应该能够:
- 理解
[id]的含义- 理解多层动态路由为什么返回对象
- 理解
[...slug]为什么返回数组- 理解
[[optional]]可选参数与前面三种的区别- 用
validate控制参数合法性- 看到文件名,就知道
params长什么样
一、这个知识解决什么问题?
上一章我们已经知道 pages/about.vue → /about(文件路由的原理)。那如果页面不是固定的呢?
商品详情 /product/1001、用户详情 /user/888……难道要创建无数个文件?显然不现实。
于是,动态路由出现了。这一章的关键不是记住"怎么写 [id]",而是理解 Nuxt 为什么用 []、... 这些符号,以及拿到 params 之后容易在哪里翻车。
二、我们的讨论过程
第一层:普通动态参数
创建 pages/product/[id].vue,访问 /product/1001 和 /product/9527 都成功。
[id] 是占位符(Placeholder),Nuxt 会把 1001 保存到 route.params.id:
route.params // → { id: "1001" }
第二层:多参数
pages/[category]/[id].vue,访问 /frontend/1001:
这是讨论最久的问题——为什么 route.params 不是嵌套对象?
因为 Nuxt 把每一个 [] 都看成一个参数,最终组成扁平的参数集合:
{ category: "frontend", id: "1001" }
params 是参数集合(Parameter Map),不是树。
第三层:Catch All
pages/[...slug].vue 访问 /docs/nuxt/router/install:
route.params // → { slug: ["docs", "nuxt", "router", "install"] }
为什么是数组?因为不知道到底有几层,Nuxt 只能全部收集起来。
第四层:可选参数
pages/[[slug]].vue,多了一对中括号。它表示"这段路径可有可无":
| 访问 URL | params |
|---|---|
/ |
{}(slug 为 undefined) |
/guides |
{ slug: "guides" } |
/a/b/c |
{ slug: ["a", "b", "c"] } |
有没有 ... 决定了是"单个可选段"还是"剩余路径全要"。[[slug]] 最典型的用途是让同一套页面既支持首页、也支持多级路径,但它的规则也最容易记混。
一个必须接受的设定:参数全是字符串
无论 URL 里写的是 1001 还是 9527,Nuxt 拿到的 params.id 都是字符串 "1001"。
URL 本身没有数字类型,路径参数永远是 String。
需要做数学运算或比较时,自己转换:
const id = Number(route.params.id)
三、最终结论
Nuxt 的动态路由有四种写法:
| 文件名 | 访问 URL | params 结果 |
|---|---|---|
[id].vue |
/1001 |
{ id: "1001" } |
[category]/[id].vue |
/frontend/1001 |
{ category: "frontend", id: "1001" } |
[...slug].vue |
/a/b/c |
{ slug: ["a", "b", "c"] } |
[[slug]].vue |
/ 或 /a/b |
{ slug: undefined } 或 { slug: ["a", "b"] } |
四、脑图
推导顺序:
- 把文件路径和实际 URL 逐段对齐
- 普通文字是固定路径,不进入
params - 每个
[name]取出对应的一段,成为params.name [...name]收集剩余所有段,得到数组[[name]]同样收集,但允许零段匹配
记住:URL 参数默认都是字符串,多个 [] 组成扁平参数对象,不会因目录嵌套产生嵌套对象。
五、代码实验
创建 pages/product/[id].vue:
<script setup>
const route = useRoute()
const id = Number(route.params.id)
</script>
<template>
<p>当前商品 ID:{{ route.params.id }}(类型:{{ typeof route.params.id }})</p>
<p>转换后:{{ id }}(类型:{{ typeof id }})</p>
</template>
访问 /product/1001,页面会显示 "1001" 是字符串、1001 是数字。
再试试给参数加校验。在 [id].vue 里补充:
definePageMeta({
validate: (route) => {
return /^\d+$/.test(route.params.id)
}
})
访问 /product/1001 正常,访问 /product/abc 会直接进入 404,而不是让页面自己判断。这个机制比在组件里写 if 更早、更干净。
另外,路由参数经常是数据请求的输入。下一章会讲到参数变化如何自动触发重新请求(为什么 useFetch 不是 fetch)。
六、版本说明
本系列以 Nuxt 3.x 的默认目录结构为例:动态路由写在 pages/ 下。Nuxt 4 把页面目录移到了 app/pages/,但 []、[...]、[[...]] 的推导规则完全一致,本节内容同样适用。
七、容易踩坑
❌ [category]/[id] 会形成嵌套对象
不会,永远都是扁平的 { category, id }。
❌ [...slug] 是字符串
不是,永远是数组;取第一段要写 route.params.slug[0]。
❌ [id] 是 Number
不是,URL 参数全部都是 String,需要自己转换:Number(route.params.id)。
❌ [[slug]] 和 [...slug] 等价
不是。[[slug]] 允许零段匹配(根路径也命中),[...slug] 至少需要一段。
❌ 参数不合法就在组件里报错
更好的做法是用 validate 在路由层直接返回 404,避免无意义的渲染。
想对比 Nuxt 和 Next.js 在路由设计上的差异,可以看 Next.js 路由指南。
一句话记忆
一个
[]对应一个参数;...收集剩余路径返回数组;[[...]]表示这段可有可无;所有参数都是字符串。