01|路由表与匹配
第六章概览 / 01。目标:把路由表理解为“URL → matched route records”的解析规则,而不是菜单数组。
最小 Router 4 配置
// src/router.ts
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from './views/HomeView.vue'
import UserView from './views/UserView.vue'
import NotFoundView from './views/NotFoundView.vue'
export const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{ path: '/', name: 'home', component: HomeView },
{ path: '/users/:id', name: 'user', component: UserView },
{ path: '/:pathMatch(.*)*', name: 'not-found', component: NotFoundView },
],
})2
3
4
5
6
7
8
9
10
11
12
13
14
// src/main.ts
createApp(App).use(router).mount('#app')2
history 决定 URL 如何与浏览器会话历史连接;routes 是 route records。访问 /users/42 时匹配 user,并得到字符串参数 route.params.id === '42'。catch-all 的 * 让 pathMatch 是可重复参数,使用命名路由生成 404 地址时可传字符串数组。
静态、动态与自定义正则
const routes = [
{ path: '/orders/new', name: 'order-new', component: NewOrderView },
{ path: '/orders/:id(\\d+)', name: 'order', component: OrderView },
{ path: '/articles/:slug?', name: 'article', component: ArticleView },
{ path: '/chapters/:parts+', name: 'chapters', component: ChaptersView },
]2
3
4
5
6
| 写法 | 匹配 | params 形状 |
|---|---|---|
/orders/new | 只匹配固定文本 | 无 |
/orders/:id(\\d+) | id 只允许数字 | string |
/articles/:slug? | slug 有或没有 | string 或空值 |
/chapters/:parts+ | 至少一个路径段 | string[] |
JavaScript 字符串里的反斜杠要转义,所以数字约束写成 \\d+。动态正则末尾的 */+ 属于参数修饰符,不是正则本身。
Vue Router 4 会对路由模式评分,静态段通常比动态段更具体,因此 /orders/new 不会因为写在动态路由后就必然被 :id 抢走。不要把数组顺序当主要优先级控制;用静态段、明确正则和 router.resolve() 测试合同。完全相同/别名等价的记录仍应避免歧义。
用 router.resolve() 做无副作用匹配实验
const order = router.resolve('/orders/42')
console.log(order.name, order.params.id, order.matched.length)
const unknown = router.resolve('/orders/nope')
console.log(unknown.name) // not-found
const chapters = router.resolve({
name: 'chapters',
params: { parts: ['vue', 'router'] },
})
console.log(chapters.href) // /chapters/vue/router(base 可能不同)2
3
4
5
6
7
8
9
10
11
resolve() 规范化位置并返回 href/matched/name/params,但不会改变当前 URL,也不会运行导航守卫。它适合路由表单元测试;真正的导航行为还要用 router.push() 和浏览器验证。
三种 history owner
| 创建函数 | 使用场景 | 责任 |
|---|---|---|
createWebHistory(base?) | 普通浏览器 SPA | 干净 URL;服务器必须配置 fallback |
createWebHashHistory(base?) | 无法控制服务器 | hash 后路径不发送给服务器 |
createMemoryHistory(base?) | SSR、Node 测试 | 不读写浏览器 URL,也不会自动完成初始导航 |
Memory history 在 SSR 中需要手动 router.push(requestUrl),再等待 router.isReady()。客户端初始导航也是异步的;依赖首个 route 完成后再挂载时,应等待 isReady()。
404 与服务端 fallback 是两层
catch-all 只处理应用已经加载后的未知 URL。若服务器对 /users/42 直接返回自己的 404,Vue Router 根本没有运行。生产服务应先区分 /api/**、真实资源和前端页面路径,再只对页面路径返回 index.html;最终由 catch-all 显示应用 404,并设置合适的服务端状态/SEO 策略。
失败路径
- 用旧的 Router 3 catch-all
*:Router 4 要使用自定义参数正则。 - 给两条 route record 使用同一个 name:名称应全局唯一,后加入记录可能替换解析目标。
- 把数字正则当数据校验:匹配成功只证明 URL 形状,服务端仍要验证资源和权限。
- 只测试站内点击:深链刷新、base 路径和服务器 fallback 仍可能失败。
- 写极端正则并与重复参数组合:可能产生灾难性回溯;保持模式简单并测试长输入。
练习:文件路由表
配置 /files/:path+(至少一段)、/reports/:year(\\d{4})/:month(\\d{2})? 和 catch-all。要求只用 createMemoryHistory() 与 router.resolve() 验证:文件 path 是数组;合法年份命中 report;非法年份进入 404;命名生成两段文件路径得到正确 href。
变化题:解释部署到 /admin/ 子路径时 base 放在哪里,以及为什么只改 Router 还不够。
参考答案:第 01 题。
本页结论
route record 负责结构匹配,history 负责位置历史,服务器负责把深链交给应用。三者任何一层缺失,站内点击成功都不能证明路由可用。