主题
Vue Router 4 使用笔记
基于 Vue 3 + TypeScript 与 vue-router 4.x,依次说明安装、路由表与实例创建、嵌套路由、404 兜底、声明式与编程式导航、重定向、路由传参、导航守卫、路由元信息、懒加载、router-link 定制以及动态挂载路由。
一、安装
bash
yarn add vue-routervue-router 4.x 与 Vue 3 配套使用;Vue 2 项目对应 3.x 版本,写法差异见 Vue Router 3 使用笔记。
二、基本使用
基本使用分为四步:定义路由表 → 创建路由实例 → 注册路由 → 放置路由出口。
1. 定义路由
新建 router/routes.ts:
ts
const routes = [
{
path: "/login",
component: () => import("@/pages/login.vue"), // 路由懒加载
},
{
path: "/home",
component: () => import("@/pages/home.vue"),
},
];
export default routes;2. 创建路由实例
新建 router/index.ts:
ts
import { createRouter, createWebHistory } from "vue-router";
import routes from "./routes";
const router = createRouter({
history: createWebHistory(), // 可传参数配置 base 路径,例如 '/app'
routes,
});
export default router;3. 路由注册
修改 main.ts,把 router 安装到应用上:
ts
import router from "./router/index";
const app = createApp(App);
app.use(router); // 注册路由4. 定义路由出口
修改 App.vue:
vue
<template>
<router-view v-slot="{ Component }">
<Transition name="fade" mode="out-in">
<component :is="Component" />
</Transition>
</router-view>
</template>
<style lang="scss">
.fade-enter-active,
.fade-leave-active {
transition: all 0.2s ease;
}
.fade-enter-from,
.fade-leave-active {
opacity: 0;
}
</style>要点说明:
<router-view>显示与 URL 对应的组件,可以放在任意位置以适应布局。v-slot拿到当前匹配到的组件,再用<component :is>绑定动态组件。<Transition>为路由切换提供过渡动效,mode="out-in"表示先离场再进场。
三、嵌套路由
App.vue 中定义的 <router-view> 是顶层出口,渲染最高级路由匹配到的组件。如果要实现登录之后左侧菜单栏不变、右侧随路由切换显示不同内容,就需要使用嵌套路由。
1. 定义路由配置
修改 router/routes.ts,在 /home 下增加 children:
ts
const routes = [
{
path: "/login",
component: () => import("@/pages/login.vue"),
},
{
path: "/home",
component: () => import("@/pages/home.vue"),
children: [
{
path: "/home/user",
component: () => import("@/pages/user.vue"),
},
{
path: "/home/manage",
component: () => import("@/pages/manage.vue"),
},
],
},
];
export default routes;2. 定义嵌套路由出口
修改 pages/home.vue,在布局中再放一个 <router-view>:
vue
<template>
<div>
菜单栏
<router-view v-slot="{ Component }">
<Transition name="fade" mode="out-in">
<component :is="Component" />
</Transition>
</router-view>
</div>
</template>访问 http://127.0.0.1:5173/home/manage 与 http://127.0.0.1:5173/home/user 可查看效果。
四、配置 404 页面
修改 router/routes.ts,追加通配路由:
ts
const routes = [
// ...
// 添加(放在最后)
{
path: "/:pathMatch(.*)*",
component: () => import("@/pages/notFound.vue"),
},
];注意:通配路由必须放在路由表最后,否则会抢先匹配掉后面所有路径。
五、声明式与编程式导航
1. 声明式导航
在模板中通过 <router-link> 完成跳转:
html
<router-link to="/home">跳转 home</router-link>2. 编程式导航
组合式 API 中通过 useRouter() 拿到路由实例再调用跳转方法:
vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
const handleManage = () => {
router.push("/home/manage");
};
</script>六、重定向
情景:在嵌套路由中,访问 /home 时希望直接进入 /home/user。
修改 router/routes.ts,给父路由加 redirect:
ts
{
path: '/home',
component: () => import('@/pages/home.vue'),
redirect: '/home/user', // 新增
children: [
{
path: '/home/user',
component: () => import('@/pages/user.vue'),
},
{
path: '/home/manage',
component: () => import('@/pages/manage.vue'),
},
],
}访问 http://127.0.0.1:5173/home 时会自动重定向到 http://127.0.0.1:5173/home/user。
七、路由传参
1. query 传参
页面传参:
vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
const handleManage = () => {
router.push({
path: "/home/manage",
query: {
plan: "123",
},
});
};
</script>页面接参:
vue
<script setup lang="ts">
import { useRoute } from "vue-router";
const route = useRoute();
console.log(route.query.plan); // query 接参
</script>query 参数会拼接在 URL 上(/home/manage?plan=123),刷新页面不会丢失。
2. 动态路由匹配
定义路由时,动态字段以冒号开头:
ts
{
path: '/register/:plan', // 动态字段以冒号开始
component: () => import('@/pages/register.vue'),
}页面传参:
vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
const handleManage = () => {
router.push("/register/123");
};
</script>页面接参:
vue
<script setup lang="ts">
import { useRoute } from "vue-router";
const route = useRoute();
console.log(route.params.plan); // params 接参
</script>3. 命名路由 params 传参(已废弃)
原始笔记中标注该方式已废弃:vue-router 4.x 不再允许向没有对应动态字段的路由传递 params,未匹配上的参数会被直接丢弃,既不进入 URL,刷新后也拿不到。需要携带参数时改用 query 或动态路由匹配。
4. 几种传参方式对比
| 方式 | 路由定义 | 传参 | 获取 | URL 形式 |
|---|---|---|---|---|
| query | path: '/home/manage' | push({ path, query }) | route.query.plan | /home/manage?plan=123 |
| 动态路由 | path: '/register/:plan' | push('/register/123') | route.params.plan | /register/123 |
| 命名路由 | name: 'register' | push({ name, params }) | route.params.plan | /register/123(需动态字段) |
八、导航守卫
1. 全局前置守卫
使用场景:做登录判断,未登录用户跳转到登录页。修改 router/index.ts:
ts
router.beforeEach((to, from) => {
if (to.path === "/login") {
// 在登录页做清除操作,如清除 token 等
}
if (!localStorage.getItem("token") && to.path !== "/login") {
// 未登录且访问的不是登录页,重定向到登录页面
return "/login";
}
});2. 路由独享守卫
使用场景:部分页面不需要登录,部分页面需要登录才能访问。修改 router/routes.ts:
ts
const auth = () => {
if (!localStorage.getItem("token")) {
// 未登录,重定向到登录页面
return "/login";
}
};
const routes = [
// ...
{
path: "/home",
component: () => import("@/pages/home.vue"),
redirect: "/home/user",
children: [
{
path: "/home/user",
component: () => import("@/pages/user.vue"),
},
{
path: "/home/manage",
component: () => import("@/pages/manage.vue"),
beforeEnter: auth, // 路由独享守卫
},
],
},
];3. 组件内守卫
使用情景:预防用户在还未保存修改前突然离开。该导航可以通过返回 false 来取消:
vue
<script setup lang="ts">
import { onBeforeRouteLeave } from "vue-router";
// 与 beforeRouteLeave 相同,无法访问 this
onBeforeRouteLeave((to, from) => {
const answer = window.confirm("确定离开吗");
// 取消导航并停留在同一页面上
if (!answer) return false;
});
</script>九、路由元信息
将自定义信息附加到路由上,例如页面标题、是否需要权限、是否开启页面缓存等。
使用情景:路由元信息 + 全局前置守卫,实现部分页面不需要登录、部分页面需要登录才能访问。
修改 router/routes.ts,给需要权限的路由加 meta:
ts
const routes = [
// ...
{
path: "/home",
component: () => import("@/pages/home.vue"),
redirect: "/home/user",
children: [
{
path: "/home/user",
component: () => import("@/pages/user.vue"),
},
{
path: "/home/manage",
component: () => import("@/pages/manage.vue"),
meta: {
title: "管理页", // 页面标题
auth: true, // 需要登录权限
},
},
],
},
];修改 router/index.ts,在守卫中读取 to.meta:
ts
router.beforeEach((to, from) => {
if (!localStorage.getItem("token") && to.meta.auth) {
// 此路由需要授权,请检查是否已登录
// 如果没有,则重定向到登录页面
return {
path: "/login",
// 保存我们所在的位置,以便以后再来
query: { redirect: to.fullPath },
};
}
});提示:
query.redirect记录了来源地址,登录成功后可以用它跳回原页面;meta.title则常在全局后置守卫里配合document.title使用。
十、路由懒加载
使用 () => import() 方式导入的组件,只会在第一次进入页面时才加载对应路由的组件:
ts
const UserDetails = () => import("./views/UserDetails.vue");
const router = createRouter({
routes: [{ path: "/users/:id", component: UserDetails }],
});webpack 下可以通过魔法注释命名 chunk:
js
const UserDetails = () =>
import(/* webpackChunkName: "group-user" */ "./UserDetails.vue");Vite 会自动根据组件文件名命名 chunk,无需手动写注释。
十一、router-link
router-link 组件默认渲染为 <a> 标签。vue-router 3.x 中可通过 tag 属性更改标签名、event 属性更改触发事件;vue-router 4.x 中这两个属性已被删除,改为通过作用域插槽(子组件向父组件传值的插槽)实现自定义导航标签。
示例:将导航标签改为 div,且需双击触发。
vue-router 3.x:
html
<router-link to="/home/user" tag="div" event="dblclick">跳转 user</router-link>vue-router 4.x:
vue
<router-link v-slot="{ href, navigate, isExactActive }" to="/home/user" custom>
<div :class="{ active: isExactActive }" :href="href" @dblclick="navigate">
跳转 user
</div>
</router-link>说明:custom 让 router-link 不再渲染默认的 <a>,导航能力完全交给插槽内容;插槽提供的 href、navigate、isExactActive 需要自己绑定到自定义元素上。
十二、动态路由
与动态路由匹配(/user/:id)不同,动态路由指手动向路由表中添加原本不存在的路由,通常用于权限校验:没有对应权限时,该地址直接访问会落到 404。
1. 修改 router/routes.ts
ts
const routes = [
{
path: "/login",
component: () => import("@/pages/login.vue"),
},
{
path: "/register/:plan",
component: () => import("@/pages/register.vue"),
},
{
path: "/home",
name: "Home", // 增加 name,动态路由通过 name 挂载到该子路由下
component: () => import("@/pages/home.vue"),
redirect: "/home/user",
children: [
{
path: "/home/user",
component: () => import("@/pages/user.vue"),
},
],
},
{
path: "/:pathMatch(.*)*",
component: () => import("@/pages/notFound.vue"),
},
];
// 将 /home/manage 拆出来
export const manageRoute = {
path: "/home/manage",
component: () => import("@/pages/manage.vue"),
};
export default routes;2. 修改 pages/login.vue
vue
<script setup lang="ts">
localStorage.setItem("role", "admin"); // 在登录页存储用户等级
</script>3. 修改 App.vue
新增代码:
vue
<script setup lang="ts">
import { watch } from "vue";
import { useRouter, useRoute } from "vue-router";
import { manageRoute } from "@/router/routes";
const router = useRouter();
const route = useRoute();
watch(route, async (newVal) => {
const role = localStorage.getItem("role");
if (role && role === "admin") {
router.addRoute("Home", manageRoute);
/* 防止页面刷新,路由丢失 */
/* 在动态路由页面刷新时,matched 数组为空 */
if (!newVal.matched.length && newVal.fullPath === "/home/manage") {
await router.replace("/home/manage");
}
}
});
</script>提示:
router.addRoute(name, route)会把路由挂到指定名称的父路由下;动态路由只存在于内存,刷新页面即丢失,因此刷新时需要重新添加并replace一次当前地址,让matched重新匹配。