# 前言

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/managehttp://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>

# 十二、动态路由

与动态路由匹配不同,动态路由是手动添加路由表中没有的路由,通常用在权限校验中,如果没有该权限,直接访问该路由失败

  1. 修改 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;
  1. 修改 pages/login.vue
<script setup lang="ts">
  localStorage.setItem('role', 'admin'); // 在登录页存储用户等级
</script>;
  1. 修改 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 页面