动态路由与参数
本教程共 50 篇 · 第 17 篇 · 更新于 2026-08-08 · 约 5 分钟阅读
本节目标:掌握用方括号定义动态路由,能读取 URL 里的参数,并理解可选参数与全匹配路由。
17-1
第 13 章的 /about、/index 都是「写死的」路由,一个文件对应一个固定网址。但真实网站常有成百上千个同类页面:商品详情 /product/1、/product/2,文章 /post/hello-world……你不可能为每一个都新建一个文件。
动态路由(dynamic routes)就是干这个的:用一对方括号 [ ] 把文件名或目录名包起来,那一段就会变成「通配参数」,能匹配任意值。比如 app/pages/product/[id].vue,访问 /product/1 和 /product/2 都会命中它,1、2 就是 id 参数的值。
17-2
在动态页面里,通过 useRoute() 组合式函数拿到当前路由对象,参数就在 route.params 上。参数名就是你方括号里写的名字。
<script setup lang="ts">
const route = useRoute()
// 访问 /product/1 时,route.params.id 的值是字符串 '1'
console.log(route.params.id)
</script>
<template>
<h1>商品编号:{{ route.params.id }}</h1>
</template>
Note
route.params.id始终是字符串类型,即使网址里写的是数字。要做数值运算,记得用Number(route.params.id)转换一下,否则'1' + 1会得到'11'这种拼接结果。
你也可以在模板里直接用 $route.params.id,这是 Vue 注入的全局属性,和 useRoute() 是同一份数据,只是写法不同。
17-3
方括号不仅能单独出现,还能和普通文字拼在一起,做出「半固定」的路由。例如:
-| app/pages/
---| users-[group]/
-----| [id].vue
访问 /users-admins/123 时,模板里能同时拿到两组参数:
<template>
<p>{{ $route.params.group }} - {{ $route.params.id }}</p>
</template>
渲染结果就是 admins - 123。这种写法在「按分组归类、又各有编号」的场景很顺手。
17-4
有时候参数可有可无。比如 / 和 /test 都想命中同一个页面,就把参数用双套方括号 [[ ]] 包起来:
-| app/pages/
---| [[slug]].vue
[[slug]].vue 能同时匹配 / 和 /test,slug 在前者不存在、在后者是 'test'。注意区别:[slug].vue 只匹配有值的情况(如 /test),不匹配根路径 /。
17-5
如果有一类「兜底」页面——凡是没被其它具体路由接住的网址都交给它处理,就用 [...slug].vue 这种全匹配(catch-all)写法:
<template>
<p>{{ $route.params.slug }}</p>
</template>
访问 /hello/world 时,route.params.slug 是一个数组 ['hello', 'world']。这常用于博客、文档站做「按路径渲染内容」的通用页。
Warning全匹配路由会把「几乎所有没匹配到的路径」都吃掉。如果只想做个 404 兜底,要小心别让它盖住了你真正的页面。通常结合第 14 章讲的
validate做校验,或在路由表里把更具体的路由放在前面。
17-6
一条路径里可以出现多个动态段,比如 app/pages/[category]/[product].vue。访问 /book/nuxt-guide 就分别得到 category='book'、product='nuxt-guide'。
Nuxt 有个明确的优先级规则:具名父路由优先于嵌套动态路由。也就是说,/foo/hello 会优先匹配 pages/foo.vue,而不是 pages/foo/[slug].vue。如果你希望 /foo 和 /foo/hello 用不同页面,正确做法是建 pages/foo/index.vue 和 pages/foo/[slug].vue,让它们各司其职。
17-7
动态路由的方括号语法在 Nuxt 3 与 Nuxt 4 完全一致,仅目录位置从 pages/ 变为 app/pages/。可选参数([[ ]])和全匹配([...slug])也是两版通用的能力。
17-8
useRoute() 拿到的不只有 params。网址里 ? 后面的查询参数在 route.query,# 后面的锚点在 route.hash。比如访问 /search?name=vue#top,route.query.name 是 'vue'、route.hash 是 '#top'。动态段管「路径里的变量」,query 管「可选的筛选条件」,各司其职。
17-9
动态路由有时会收到不合法的参数,比如 /product/abc 里 abc 不是数字。第 14 章提过的 validate 这时就派上用场:在页面 definePageMeta 里返回 false,Nuxt 会把它当作无效路由、直接给 404。
<script setup lang="ts">
definePageMeta({
validate (route) {
return typeof route.params.id === 'string' && /^\d+$/.test(route.params.id)
},
})
</script>
Tip校验逻辑复杂时,与其塞在
validate里,不如改用第 19 章的匿名中间件,写法更灵活,还能顺手做重定向。
17-10
动态路由最适合「同一套模板、不同数据」的页面:商品详情、用户主页、博客文章、文档章节。它的核心价值是「一套代码服务无数个类似页面」,你只管在组件里读 route.params,再去取对应数据渲染。不要把「完全不同结构」的页面硬塞进同一个动态文件,那样分支逻辑会很难维护,反而失去动态路由的简洁。
17-11
你写的 [id].vue 文件,Nuxt 在构建时会读一遍 app/pages 目录,按命名规则翻译成 Vue Router 的路由配置。核心规则就一条:方括号 [ ] 被翻译成「冒号 + 参数名」,所以 app/pages/product/[id].vue 在内部等价于路径 /product/:id。
你不需要手写这份配置——Nuxt 内置的路由生成器(基于 unjs/radix3)帮你完成扫描和拼装。这也是为什么「目录结构」和「可访问网址」能始终一致:它们本来就是同一份来源,文件动了,路由表就跟着动。理解了这层,你就明白为什么加了 [id].vue 不用去任何地方「注册」它。
Note路由生成发生在构建/启动阶段。所以动态段的命名(方括号里的词)决定了
route.params上的键名——文件叫[id].vue,参数键就是id;文件叫[slug].vue,键就是slug。改名等于换键名,引用处要同步改。
17-12
这是动态路由最高频的坑:在 /product/1 和 /product/2 之间切换时,Vue 为了性能会复用同一个组件实例,不会销毁重建。结果就是 <script setup> 里的初始化逻辑只跑一次;你若在里面把 route.params.id 存进一个普通变量,切到 /product/2 时那个变量不会自动更新。
正确做法是依赖 route 的响应性:route.params.id 本身是响应式的,直接在模板里用 {{ route.params.id }},或包进 computed、用 watch 监听变化:
<script setup lang="ts">
const route = useRoute()
const id = computed(() => route.params.id as string)
// 参数变化时重新拉数据
watch(() => route.params.id, (newId) => {
// 根据 newId 重新请求对应内容
})
</script>
这样无论从哪个商品跳到哪个商品,数据和界面都会跟着参数走,不会「卡」在第一次的值上。
17-13
再次强调:route.params 里的值永远是字符串。哪怕网址写的是 /product/42,route.params.id 也是 '42'。做比较、运算前务必转换:
- 转数字:
Number(route.params.id)或+'42'。 - 判断是否纯数字:用正则
/^\d+$/.test(route.params.id)(前面validate里就用过)。 - 复杂数据别硬塞路径:URL 路径里只能放字符串,对象、数组请走
route.query或单独接口获取。
一个实用组合拳:在 validate 里拦掉非数字 id,进入页面后就能放心 Number(),不用担心拿到 NaN 导致渲染崩掉。
Tip想看参数到底长什么样?在
<script setup>里console.log(route.params)然后打开浏览器控制台,比凭空猜更靠谱。开发阶段多打几次日志,能少踩很多「以为拿到了数字其实是字符串」的坑。
17-14
方括号 [name] 让一个文件匹配一类网址,参数用 useRoute().params 读取;双括号 [[name]] 表示可选,三点 [...name] 表示全匹配。记住参数是字符串、参数变化要 watch。下章我们看「页面里再套页面」的嵌套路由。