# 前言
vue-router4x 相对于 vue-router3x 除了新增了组合式 API 以外,还删除或变动了不少地方。单独列出来变动点太杂乱。这里系统性的把项目中经常能用到的知识点进行整理
# 一、安装
| yarn add vue-router | 
# 二、基本使用
# 1. 定义路由
新建 router/routes.ts
| const routes = [ | |
|   { | |
| path: "/login", | |
| component: () => import("@/pages/login.vue"), // 路由懒加载 | |
| }, | |
|   { | |
| path: "/home", | |
| component: () => import("@/pages/home.vue"), | |
| }, | |
| ]; | |
| export default routes; | 
# 2. 创建路由实例
新建 router/index.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
| import router from "./router/index"; | |
| const app = createApp(App); | |
| app.use(router); // 注册路由 | 
# 4. 定义路由出口
修改 App.vue
| <template> | |
| <router-view v-slot="{ Component }"> | |
| <Transition name="fade" mode="out-in"> | |
| <component :is="Component" /> | |
| </Transition> | |
| </router-view> | |
| </template> | |
| <style> | |
| .fade-enter-active, | |
| .fade-leave-active { | |
| transition: all 0.2s ease; | |
| } | |
| .fade-enter-from, | |
| .fade-leave-active { | |
| opacity: 0; | |
| } | |
| </style> | 
router-view 将显示与 url 对应的组件,可以把它放在任何地方以适应布局
Transition 是基于路由的动态过渡动效
# 三、嵌套路由
在 App.vue 中定义的 router-view,这是顶层的出口,渲染最高级路由匹配到的组件
如果要实现登录之后左侧菜单栏不变,右侧随路由的切换变化显示的内容,需使用嵌套路由
# 1. 定义路由配置文件
修改 router/routes.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
| <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
| const routes = [ | |
| ...// 添加(放在最后) | |
|   { | |
| path: "/:pathMatch(.*)*", | |
| component: () => import("@/pages/notFound.vue"), | |
| }, | |
| ]; | 
# 五、声明式、编程式导航
# 1. 声明式导航(在模板中进行路由跳转)
| <router-link to="/home"> 跳转home </router-link>; | 
# 2. 编程式导航(组合式 API)
| <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
| { | |
| 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 传参
| // 页面传参 | |
| <script setup lang="ts"> | |
| import { useRouter } from 'vue-router'; | |
| const router = useRouter(); | |
| const handleManage = () => { | |
| router.push({ | |
| path: '/home/manage', | |
| query: { | |
| plan: '123', | |
| }, | |
| }); | |
| }; | |
| </script> | |
| // 页面接参 | |
| <script setup lang="ts"> | |
| import { useRoute } from 'vue-router'; | |
| const route = useRoute(); | |
| console.log(route.query.plan); //query 接参 | |
| </script> | 
# 2. 动态路由匹配
| // 定义路由 | |
| { | |
| path: '/register/:plan', // 动态字段以冒号开始 | |
| component: () => import('@/pages/register.vue'), | |
| }, | |
| // 页面传参 | |
| <script setup lang="ts"> | |
| import { useRouter } from 'vue-router'; | |
| const router = useRouter(); | |
| const handleManage = () => { | |
| router.push('/register/123'); | |
| }; | |
| </script> | |
| // 页面接参 | |
| <script setup lang="ts"> | |
| import { useRoute } from 'vue-router'; | |
| const route = useRoute(); | |
| console.log(route.params.plan); //params 接参 | |
| </script> | 
# 3. 命名路由 params 传参(已被废弃)
# 八、导航守卫
# 1. 全局前置守卫
使用场景:做登录判断,未登陆用户跳转到登录页
修改 router/index.ts
| router.beforeEach((to, from) => { | |
| if (to.path === '/login') { | |
|       // 在登录页做清除操作,如清除 token 等 | |
|     } | |
| if (!localStorage.getItem('token') && to.path !== '/login') { | |
|       // 未登陆且访问的不是登录页,重定向到登录页面 | |
| return '/login'; | |
|     } | |
| }); | 
# 2. 路由独享守卫
使用场景:部分页面不需要登录,部分页面需要登录才能访问
修改 router/routes.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 来取消
| <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
| 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
| router.beforeEach((to, from) => { | |
| if (!localStorage.getItem("token") && to.meta.auth) { | |
|     // 此路由需要授权,请检查是否已登录 | |
|     // 如果没有,则重定向到登录页面 | |
| return { | |
| path: "/login", | |
|       // 保存我们所在的位置,以便以后再来 | |
| query: { redirect: to.fullPath }, | |
| }; | |
|   } | |
| }); | 
# 十、路由懒加载
使用 () => import () 方式导入的组件,只会在第一次进入页面时才会加载对应路由的组件
| const UserDetails = () => import('./views/UserDetails.vue') | |
| const router = createRouter({ | |
|   // ... | |
| routes: [{ path: '/users/:id', component: UserDetails }], | |
| }) | 
webpack 命名 chunk
| 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,且需双击触发
| <router-link v-slot="{ href, navigate, isExactActive }" to="/home/user" custom> | |
| <div :class="{ active: isExactActive }" :href="href" @dblclick="navigate">跳转user</div> | |
| </router-link> | 
# 十二、动态路由
与动态路由匹配不同,动态路由是手动添加路由表中没有的路由,通常用在权限校验中,如果没有该权限,直接访问该路由失败
- 修改 router/routes.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; | 
- 修改 pages/login.vue
| <script setup lang="ts"> | |
| localStorage.setItem('role', 'admin'); // 在登录页存储用户等级 | |
| </script>; | 
- 修改 App.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> | 
如果 localStorage.getItem ('role') 的值不为 admin,直接访问 /home/manage,会返回 404 页面
