Skip to content

Vue Router 4 使用笔记 ​

基于 Vue 3 + TypeScript 与 vue-router 4.x,依次说明安装、路由表与实例创建、嵌套路由、404 兜底、声明式与编程式导航、重定向、路由传参、导航守卫、路由元信息、懒加载、router-link 定制以及动态挂载路由。

一、安装 ​

bash
yarn add vue-router

vue-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 形式
querypath: '/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 组件默认渲染为 <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 重新匹配。