Vue Router 5:手写路由与类型安全的文件路由
Vue Router 5:手写路由与类型安全的文件路由
系列导航:Vue 3 现代开发指南
上一篇:跨层通信与模板引用
下一篇:Pinia 4 状态管理
Vue Router 5 是 Vue 的官方路由方案。它保留了 Vue Router 4 的手写路由 API,并把原 unplugin-vue-router 的文件路由和类型生成能力合并进核心包。普通 Router 4 项目若没有使用该插件,升级到 5 通常不需要修改路由代码。
本文先讲所有项目都适用的核心 API,再介绍 Router 5 的文件路由。两种模式选一种即可,不需要同时维护两套路由表。
一、安装和注册
使用 create-vue 时可以直接选择 Vue Router。手工安装:
npm install vue-router@5
创建 src/router/index.ts:
import {
createRouter,
createWebHistory,
type RouteRecordRaw,
} from 'vue-router'
const routes: RouteRecordRaw[] = [
{
path: '/',
redirect: { name: 'home' },
},
{
path: '/home',
name: 'home',
component: () => import('@/views/HomeView.vue'),
},
{
path: '/about',
name: 'about',
component: () => import('@/views/AboutView.vue'),
},
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
scrollBehavior: () => ({ top: 0 }),
})
export default router
路由页面使用动态导入,可以在生产构建中按路由拆包。入口文件注册路由:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App)
.use(router)
.mount('#app')
根组件放置导航和出口:
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
</script>
<template>
<nav aria-label="主导航">
<RouterLink :to="{ name: 'home' }">首页</RouterLink>
<RouterLink :to="{ name: 'about' }">关于</RouterLink>
</nav>
<RouterView />
</template>
RouterLink 最终会渲染可访问的链接;普通站内跳转应优先使用它,而不是给按钮绑定 router.push()。
二、History 与 Hash 模式
createWebHistory() 生成没有 # 的常规 URL:
history: createWebHistory(import.meta.env.BASE_URL)
生产服务器必须把未知前端路径回退到 index.html,同时不要错误吞掉真正的静态资源和 API 404。Nginx 常见配置思路是:
location / {
try_files $uri $uri/ /index.html;
}
无法配置服务器回退时,可以使用 Hash:
import { createWebHashHistory } from 'vue-router'
history: createWebHashHistory(import.meta.env.BASE_URL)
Hash 后的内容不会发送给服务器,部署简单,但 URL 中会出现 #。
三、动态参数与 Props
声明用户详情路由:
import type { RouteRecordRaw } from 'vue-router'
const userDetailRoute = {
path: '/users/:id',
name: 'user-detail',
component: () => import('@/views/UserDetailView.vue'),
props: (route) => ({ id: String(route.params.id) }),
} satisfies RouteRecordRaw
通过命名路由跳转:
<RouterLink
:to="{
name: 'user-detail',
params: { id: user.id },
}"
>
{{ user.name }}
</RouterLink>
页面把路由参数当普通 Prop 接收:
<script setup lang="ts">
defineProps<{ id: string }>()
</script>
这种方式让页面组件更容易测试,也减少组件对 useRoute() 的直接依赖。
使用对象跳转并携带 params 时,应使用 name;如果提供 path,额外的 params 会被忽略。未在路径中声明的临时信息应放在 query,需要长期存在的数据则应由 API 或 Store 管理。
四、Query 参数和编程式导航
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const keyword = computed(() => {
const value = route.query.q
return typeof value === 'string' ? value : ''
})
function search(q: string) {
router.push({
name: 'search',
query: { q, page: '1' },
})
}
function closeModal() {
router.back()
}
</script>
Query 值可能是字符串、字符串数组、null 或缺失值,使用前要做类型收窄。URL 参数最终都是文本,不要假设 route.params.id 或 route.query.page 自动成为数字。
常用导航方法:
router.push():增加一条历史记录;router.replace():替换当前记录;router.back()/router.forward():前进后退;<RouterLink replace>:声明式替换当前记录。
五、嵌套路由
父页面必须包含自己的 <RouterView>:
import type { RouteRecordRaw } from 'vue-router'
const settingsRoute = {
path: '/settings',
component: () => import('@/views/settings/SettingsLayout.vue'),
children: [
{
path: '',
name: 'settings-profile',
component: () => import('@/views/settings/ProfileSettings.vue'),
},
{
path: 'security',
name: 'settings-security',
component: () => import('@/views/settings/SecuritySettings.vue'),
},
],
} satisfies RouteRecordRaw
子路由的 path 不以 / 开头,最终路径分别是 /settings 和 /settings/security。
六、导航守卫
导航守卫可以返回目标位置或 false,不必调用旧式 next():
router.beforeEach(async (to) => {
const requiresAuth = to.meta.requiresAuth === true
const signedIn = await checkSession()
if (requiresAuth && !signedIn) {
return {
name: 'login',
query: { redirect: to.fullPath },
}
}
})
避免在模块顶层、Pinia 尚未安装前创建 Store。需要在守卫中访问 Store 时,在守卫回调里调用 useXxxStore(),或显式传入已创建的 Pinia 实例。
元信息可以通过模块扩展类型化:
import 'vue-router'
declare module 'vue-router' {
interface RouteMeta {
requiresAuth?: boolean
title?: string
}
}
七、Router 5 的文件路由
Router 5 内置文件路由插件,可以根据 src/pages 自动生成路由和类型。配置 vite.config.ts:
import { fileURLToPath, URL } from 'node:url'
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
import VueRouter from 'vue-router/vite'
export default defineConfig({
plugins: [
VueRouter({
dts: 'src/route-map.d.ts',
}),
// Vue 插件必须放在 VueRouter 后面
vue(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
})
创建文件:
src/pages/
├── index.vue # /
├── about.vue # /about
└── users/
└── [id].vue # /users/:id
入口使用生成的路由:
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { handleHotUpdate, routes } from 'vue-router/auto-routes'
import App from './App.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
})
if (import.meta.hot) {
handleHotUpdate(router)
}
createApp(App).use(router).mount('#app')
启动开发服务器后会生成类型文件。把生成文件提交到仓库,并确保它包含在 TypeScript 配置中。官方还提供 Vue 语言工具插件,让页面内的 useRoute() 根据当前文件推断参数类型。
文件路由适合路由较多、重视参数类型和约定式目录的项目;路由很少或有大量动态配置时,手写 routes 数组依然简单可靠。
八、从 Router 4 升级
Router 5 是过渡版本:
- 未使用
unplugin-vue-router的 Router 4 项目没有业务 API 破坏性变化; - 使用文件路由插件的项目主要需要把导入路径迁到
vue-router/vite、vue-router/auto-routes等新入口; - Router 5 为未来 ESM-only 的 Router 6 提供迁移窗口,应逐步清理弃用 API。
小结
核心路由 API 仍围绕 createRouter()、History、路由记录、RouterLink 和 RouterView。页面组件优先懒加载,动态参数优先通过 Props 接收。Router 5 的新增价值主要是内置类型安全的文件路由;小项目继续手写路由完全没有问题。