Skip to content

Vue Router 路由详解

单页应用(SPA)只有一个 HTML 页面,但用户需要"页面感"——点导航变地址、能后退、能分享链接。路由就是解决这个问题的:地址栏 URL 和页面内容的一一对应关系。Vue 生态里做这件事的就是 Vue Router(本文基于 Vue Router 4,对应 Vue 3)。

先看一个典型场景:BinMaker 给公司做了一套后台管理系统,左侧菜单十几个模块——资产管理、采购管理、报表中心……如果是传统多页应用,每个菜单就是一个 .html 文件,切菜单就是整页刷新。SPA 的玩法不同:只有一个 index.html,切菜单时用 JS 换掉中间的内容区。但这样做有个麻烦:地址栏永远停在 /#/,用户 F5 刷新直接回到首页,想给同事分享"资产详情页"只能靠嘴描述。

Vue Router 解决的就是这三件事:

  • URL 与组件绑定/assets 显示资产列表组件,/assets/123 显示详情组件
  • 浏览历史:前进、后退、刷新都保持当前页面(这是 SPA 最容易丢的体验)
  • 导航控制:登录才能进的管理页、离开前的确认提示、路由级懒加载

版本对应:Vue Router 3 还是 4?

写代码之前,先把版本说清楚——Vue Router 3 配 Vue 2,Vue Router 4 配 Vue 3,两者 API 不兼容:

维度Vue Router 3(配 Vue 2)Vue Router 4(配 Vue 3)
创建方式new VueRouter({ routes })createRouter({ history, routes })
挂载new Vue({ router })app.use(router)
history 模式mode: 'history' / 'hash'createWebHistory() / createWebHashHistory()
动态路由router.addRoutes()router.addRoute()(单条)
类型支持JS 为主原生 TS 重写,类型完备
现状维护模式,仅兼容 Vue 2活跃开发,本文基于此

判断口诀:看到 new VueRouter 是 3 代,看到 createRouter 是 4 代。老项目升级 Vue 3 时必须同步升级 Router。

路由怎么工作:一张流程图

Vue Router 4 的核心机制分三块:路由表(URL 与组件的映射)、history(地址栏变化怎么感知)、匹配与渲染(URL 变了怎么换组件)。流程如下:

Vue Router 工作流程

流程拆开看:用户在地址栏输入或点击 <router-link> 触发导航 → 路由表匹配出对应的组件(可能经过守卫拦截)→ 渲染到 <router-view> 出口。整个过程不刷新页面。

快速上手:写一个最简单的路由

html
<!-- App.vue -->
<nav>
  <router-link to="/">首页</router-link>
  <router-link to="/assets">资产管理</router-link>
</nav>
<!-- 路由组件渲染出口 -->
<router-view />
js
// router/index.js —— Vue Router 4 的创建方式
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import Assets from '../views/Assets.vue';

const router = createRouter({
  history: createWebHistory(), // 地址栏干净,无 # 号
  routes: [
    { path: '/', component: Home },
    { path: '/assets', component: Assets },
  ],
});

export default router;
js
// main.js —— 挂到 app 上
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';

createApp(App).use(router).mount('#app');

三个关键点:

  1. createRouter + history:Vue Router 3 的 mode 配置变成了显式的 history 函数,三种模式见下文
  2. <router-view>:路由匹配到的组件渲染到这里,类似插槽的出口
  3. <router-link>:渲染成 <a> 标签,点击不刷新页面、只更新内容区

三种 history 模式怎么选

模式创建函数地址栏形态适用场景
Web HistorycreateWebHistory()https://site.com/assets最推荐,URL 干净、利于 SEO
HashcreateWebHashHistory()https://site.com/#/assets静态托管、无服务端转发能力
MemorycreateMemoryHistory()无真实地址测试、SSR,不依赖浏览器

Web History 模式有个硬性前提:服务端要把所有路径都指向 index.html(Nginx 配 try_files $uri $uri/ /index.html),否则刷新 /assets 会 404。Hash 模式靠 # 后面的内容变化触发导航,不经过服务器,所以任何静态托管都能用,代价是 URL 带 # 不好看。

实跑验证过的一个冷知识:Memory 模式的对象只有 go() 方法、没有 back()/forward()(浏览器历史语义专属),所以它定位就是测试和 SSR 场景,别在浏览器环境当主模式用。

动态路由:/assets/123 这种地址怎么配

列表页 → 详情页是后台系统最经典的路由场景。详情页地址带 id,用路径参数

js
const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/assets', component: AssetsList },
    { path: '/assets/:id', component: AssetDetail }, // :id 是动态段
  ],
});

组件里读取参数:

html
<template>
  <div>当前资产编号:{{ route.params.id }}</div>
</template>
js
import { useRoute } from 'vue-router';
const route = useRoute();
console.log(route.params.id); // 从 URL 里解析出的 123

实跑验证的行为:

  • /assets/123 匹配 /assets/:idparams.id === '123'(字符串)
  • 用命名路由跳转更稳:router.push({ name: 'assetDetail', params: { id: '456' } }),不依赖字符串拼接

注意:从 /assets/1 切到 /assets/2 时,组件会被复用(同一个组件实例),onMounted 不会重新执行——要在 watch(() => route.params.id, ...) 里响应参数变化,或者给 <router-view :key="route.fullPath"> 强制重建。

嵌套路由:菜单有层级怎么办

后台系统的菜单几乎都是树状的——"资产管理"下挂"列表"和"详情"。嵌套路由用 children

js
const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/assets',
      component: AssetsLayout, // 父级布局(含侧边栏 + 子路由出口)
      children: [
        { path: '', component: AssetsList },      // /assets
        { path: 'create', component: AssetCreate }, // /assets/create
        { path: ':id', component: AssetDetail },   // /assets/123
      ],
    },
  ],
});

父组件 AssetsLayout.vue 里必须有 <router-view />,子路由的组件渲染在这里。实跑验证:访问 /assets/create 时,路由匹配了两层组件(父布局 + 子页面),这就是"嵌套路由 matched 两级"的含义。

导航守卫:谁有权进这个页面

后台系统逃不开权限控制——未登录用户访问管理页要踢回登录页。Vue Router 4 提供全局守卫路由级守卫组件内守卫三级机制,最常用的是全局 beforeEach

js
router.beforeEach((to, from) => {
  const token = localStorage.getItem('token');

  // to.meta 是路由配置里自定义的元信息
  if (to.meta.requiresAuth && !token) {
    return { path: '/login', query: { redirect: to.fullPath } }; // 重定向并记录来路
  }
  return true; // 放行
});
js
// 路由表里标记哪些页面需要登录
{ path: '/assets', component: AssetsList, meta: { requiresAuth: true } },

守卫函数的返回值语义(实跑验证过):

返回值行为
true放行,继续导航
false取消本次导航,停在原地
路由对象 / 字符串重定向到新地址
不返回 / undefined放行(慎用,建议显式 return true

另外两个钩子:

  • afterEach(to, from):导航完成后触发,适合做页面标题、埋点统计(没有返回值)
  • beforeRouteLeave(组件内):离开前确认,比如"表单未保存,确定离开?"

守卫的执行顺序:beforeEach(全局)→ beforeEnter(路由级)→ 组件内守卫 → afterEach(全局)。守卫可以串联多个,按注册顺序执行。

路由懒加载:首屏为什么快

后台系统页面多了,把所有组件一次性打进 bundle.js,首屏下载几百 KB 起步。路由懒加载让每个页面只在被访问时才加载:

js
// 按需加载:访问 /assets 时才下载 AssetsList 的 chunk
{ path: '/assets', component: () => import('../views/AssetsList.vue') },

Vite/webpack 会把 import() 的模块拆成独立 chunk,<router-view> 渲染前自动加载对应 chunk。这是 SPA 首屏性能优化性价比最高的一招,配合 Suspense 还能做加载态。

总结:什么时候用什么

场景用什么
地址栏干净 + 有服务端Web History(默认推荐)
纯静态托管 / 无服务端转发Hash 模式
单测 / SSRMemory 模式
详情页 / 列表页带参动态路由 :id + useRoute().params
菜单多级嵌套路由 children
登录 / 权限控制beforeEach + meta.requiresAuth
首屏优化组件懒加载 () => import()

版本提醒:本文是 Vue Router 4(配 Vue 3);如果你还在维护 Vue 2 老项目,路由写法是 new VueRouter({ mode: 'history' }),差异对照见 Vue 版本演进:Vue 2 与 Vue 3 全对比

链式导航:路由解决了"页面怎么切",那页面里的数据怎么管?看 Pinia 状态管理详解;路由组件本身就是 Vue 组件,通信方式见 Vue 组件通信全景;刚接触 Vue 先看 Vue 快速入门