吃透 Vue Router,搞定前端路由开发

140 阅读10分钟

路由的基本概念

1. 路由

在前端开发中,路由(Routing)是一个核心概念,尤其是在单页应用(SPA)中。它描述的是URL与UI之间的映射关系,即当用户访问不同的URL时,前端应用会相应地展示不同的界面或组件,而无需重新从服务器加载整个页面

2. 单页应用SPA

SPA指的是一个web网站只加载单个HTML页面,所有组件的展示与切换都在唯一的一个页面内完成。它通过路由切换界面(或组件),以及网络请求数据,实现页面的局部刷新或整体更新,而无需重新加载整个页面。 得益于无需全部重加载,SPA具有很多优点,尤其是页面切换快,减轻服务器压力。

简单理解:前端路由就像 SPA 的「导航地图」——URL 是「地址」,页面视图是「目的地」,路由负责监听地址变化,精准定位到对应的目的地,且全程不需要 “重新加载整个页面”。

3.前端路由的实现方式

前端路由的核心是 无刷新切换页面视图,核心依赖浏览器特性实现 URL 与视图的映射,主流有 3 种实现方式,简要说明如下:

1. Hash 模式(URL 锚点)

  • 原理:利用 URL 中 # 后面的「锚点部分」(hash)—— 该部分变化不会触发浏览器刷新,也不会向服务器发送请求。

利用URL的hash值(即#后面的部分)来模拟一个完整的URL,以便在前端进行路由的匹配和页面的渲染。

①用户点击路由链接,导致hash值变化

②hash值变化,触发hashchange事件(而不会重新加载页面)

③路由监听hashchange事件的触发,根据hash值的变化来更新页面的内容

简单实现:

HTML结构

<div id="app"></div>
<nav>
    <a href="#/home">首页</a>
    <a href="#/about">关于</a>
    <a href="#/contact">联系我们</a>
</nav>

JS逻辑


window.onload = function () {
    // 初始化页面
    updateView()
    // 监听hashchange事件
    window.addEventListener('hashchange', function () {
        updateView()
    })
    function updateView() {
    //根据URL进行界面或组件的切换操作,如此处的更新<div id="app"></div>的渲染内容
        const hash = window.location.hash
        let content = ''
        switch (hash) {
            case '#/home':
                content = '<h1>首页</h1>'
                break
            case '#/about':
                content = '<h1>关于</h1>'
                break
            case '#/contact':
                content = '<h1>联系我们</h1>'
                break
            default:
                content = '<h1>404 Not Found</h1>'
        }
        document.getElementById('app').innerHTML = content
    }
}
  • 优点:兼容性极强(支持所有浏览器),无需后端配置;
  • 缺点:URL 含 # 不够美观,部分场景(如 SEO、分享链接)可能受影响。

2. History 模式(HTML5 新特性)

  • 原理:基于 HTML5 的 History APIpushState()replaceState())—— 可直接修改浏览器地址栏 URL 和历史记录栈,且不触发页面刷新。

在History模式下,前端路由的实现方式与Hash模式类似,但URL的变化和监听机制不同。

①利用HTML5 History API(如history.pushState和history.replaceState方法)来实现URL的跳转,而不是直接设置window.location.hash。

②监听popstate事件,根据URL更新页面内容

由于History模式的实现相对复杂,并且需要服务器端的支持,因此在实际项目中,通常会使用Vue Router、React Router等前端路由库来简化开发。这些库已经封装了URL的修改、监听和页面内容更新的逻辑,并提供了丰富的路由配置选项。

简单实现:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>History 路由</title>
  <style>
    button { margin: 5px; }
    #app { margin-top: 10px; padding: 10px; border: 1px solid #eee; }
  </style>
</head>
<body>
  <!-- 导航按钮 -->
  <button onclick="go('/')">首页</button>
  <button onclick="go('/about')">关于</button>
  <button onclick="go('/list')">列表</button>

  <!-- 视图容器 -->
  <div id="app"></div>

  <script>
    // 路由规则
    const routes = {
      '/': '首页',
      '/about': '关于页',
      '/list': '列表页',
      '*': '404'
    };

    // 渲染视图
    const render = (path) => document.getElementById('app').textContent = routes[path] || routes['*'];

    // 导航核心:修改URL+渲染
    const go = (path) => {
      history.pushState({}, '', path);
      render(path);
    };

    // 监听前进/后退
    window.onpopstate = () => render(location.pathname);

    // 初始化
    render(location.pathname);
  </script>
</body>
</html>
  • 优点:URL 整洁(无 #),符合常规使用习惯;
  • 缺点:兼容性需 IE10+,必须后端配置支持(刷新页面时,服务器需返回前端入口文件,否则会 404)。

3. 内存路由(Memory Router)

  • 原理:不依赖 URL,路由状态存储在前端内存中(如变量、数组)

它的实现不依赖任何浏览器原生 API(如 locationhistory),完全由自定义逻辑控制,适合无 URL 需求的场景(如 Electron 桌面应用、弹窗内路由、多标签页内嵌视图等)。

核心实现思路

内存路由的实现围绕 3 个核心模块展开:

  1. 路由规则:定义「路径 → 视图 / 组件」的映射(和其他路由一致);
  2. 内存状态管理:用数组维护「路由历史栈」(存储过往访问的路径),用变量记录「当前路由索引」(定位当前所在路径);
  3. 导航与渲染:封装导航方法(跳转、前进、后退),修改内存中的路由状态,触发视图重新渲染。

简单实现:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>内存路由</title>
  <style>
    .nav button { margin: 5px; padding: 6px 12px; }
    .view { margin-top: 15px; padding: 15px; border: 1px solid #eee; }
  </style>
</head>
<body>
  <!-- 导航按钮 -->
  <div class="nav">
    <button onclick="router.push('/')">首页</button>
    <button onclick="router.push('/about')">关于</button>
    <button onclick="router.push('/list')">列表</button>
    <button onclick="router.goBack()">后退</button>
    <button onclick="router.goForward()">前进</button>
  </div>

  <!-- 视图容器 -->
  <div class="view" id="app"></div>

  <script>
    // 1. 路由规则(简化:路径 → 视图)
    const routes = {
      '/': '首页 - 内存路由示例',
      '/about': '关于页 - 不依赖URL',
      '/list': '列表页 - 状态存内存',
      '*': '404 - 页面不存在'
    };

    // 2. 简化版内存路由核心逻辑
    const router = {
      historyStack: ['/'], // 内存历史栈(默认首页)
      currentIndex: 0,     // 当前路由索引
      app: document.getElementById('app'),

      // 渲染视图(核心)
      render() {
        const path = this.historyStack[this.currentIndex];
        this.app.textContent = routes[path] || routes['*'];
      },

      // 跳转新路径
      push(path) {
        this.historyStack.splice(this.currentIndex + 1); // 清除后续历史
        this.historyStack.push(path);
        this.currentIndex = this.historyStack.length - 1;
        this.render();
      },

      // 后退
      goBack() {
        if (this.currentIndex > 0) {
          this.currentIndex--;
          this.render();
        }
      },

      // 前进
      goForward() {
        if (this.currentIndex < this.historyStack.length - 1) {
          this.currentIndex++;
          this.render();
        }
      }
    };

    // 初始化渲染
    router.render();
  </script>
</body>
</html>
  • 优点:完全脱离 URL 约束,灵活可控;
  • 缺点:无法通过 URL 导航、刷新或分享状态。

总结

  • 浏览器端 Web 应用:优先用 History 模式(需后端配合)或 Hash 模式(零配置);
  • 桌面 / 内嵌应用:可用 内存路由
  • 主流前端框架(Vue Router、React Router)均内置了这三种模式的实现,开发者无需手动处理底层事件。

 Vue router 4

Vue Router 是 Vue.js 官方的路由管理器,用于实现单页应用(SPA)的页面跳转与路由管理。它与 Vue 深度集成,支持路由嵌套、动态路由、路由守卫、懒加载等核心功能。以下从 基础配置→核心功能→高级用法→常见问题 逐步讲解,兼顾 Vue 2 和 Vue 3 差异。

一、前置准备:版本对应关系

Vue Router 版本与 Vue 版本强绑定,需先确认匹配:

Vue 版本Vue Router 版本安装命令核心 API 差异
Vue 2vue-router@3npm i vue-router@3 --savenew Router()$router/$route
Vue 3vue-router@4npm i vue-router@4 --savecreateRouter()useRouter/useRoute

以下讲解默认以 Vue 3 + Vue Router 4 为主,关键差异处会标注 Vue 2 用法。

二、基础使用:从安装到首次路由

1. 安装 Vue Router

# Vue 3
npm i vue-router@4 --save

# Vue 2(兼容写法)
npm i vue-router@3 --save

2. 目录结构设计

推荐在 src 下创建 router 文件夹,统一管理路由配置:

src/
├── router/
│   └── index.js  # 路由配置文件
├── views/        # 页面组件(路由对应组件)
│   ├── Home.vue
│   └── About.vue
├── App.vue
└── main.js

3. 核心配置:创建路由实例(router/index.js)

Vue 3 中通过 createRouter 创建路由实例,核心配置包含 history(路由模式)和 routes(路由规则):

// Vue 3:router/index.js
import { createRouter, createWebHistory } from 'vue-router'
// 导入页面组件
import Home from '../views/Home.vue'
import About from '../views/About.vue'

// 1. 定义路由规则:数组形式,每个规则对应一个路由
const routes = [
  {
    path: '/',          // 路由路径(URL 路径)
    name: 'Home',       // 路由名称(可选,用于命名路由跳转)
    component: Home     // 路由对应组件
  },
  {
    path: '/about',
    name: 'About',
    component: About
  }
]

// 2. 创建路由实例
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL), // 路由模式(HTML5 History 模式)
  routes: routes // 传入路由规则(简写:routes)
})

// 3. 导出路由实例(供 main.js 挂载)
export default router
关键说明:
  • 路由模式

    • createWebHistory():HTML5 History 模式(URL 无 #,需后端配置支持);
    • createWebHashHistory():Hash 模式(URL 带 #,无需后端配置,兼容性好);
    • Vue 2 对应:mode: 'history' 或 mode: 'hash'
  • Vue 2 配置差异

    // Vue 2:router/index.js
    import Vue from 'vue'
    import Router from 'vue-router'
    import Home from '../views/Home.vue'
    
    Vue.use(Router) // 全局注册路由插件
    
    export default new Router({
      mode: 'history', // 路由模式
      routes: [
        { path: '/', component: Home }
      ]
    })
    

4. 挂载路由到 Vue 应用(main.js)

// Vue 3:main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router' // 导入路由实例

const app = createApp(App)
app.use(router) // 挂载路由(关键步骤)
app.mount('#app')

// Vue 2 差异:
// import Vue from 'vue'
// import App from './App.vue'
// import router from './router'
// new Vue({ router, render: h => h(App) }).$mount('#app')

5. 页面中使用路由

需通过两个核心组件实现导航与渲染:

  • <router-link>:导航组件(替代 <a> 标签,避免页面刷新);
  • <router-view>:路由出口(路由对应组件会渲染到此处)。
示例:App.vue
<template>
  <div id="app">
    <!-- 1. 导航区域:router-link 生成导航链接 -->
    <nav>
      <!-- 基础用法:to 对应路由 path -->
      <router-link to="/">首页</router-link>
      <router-link to="/about">关于我们</router-link>

      <!-- 命名路由用法:to 传对象(推荐,避免硬编码 path) -->
      <router-link :to="{ name: 'Home' }">首页(命名路由)</router-link>
    </nav>

    <!-- 2. 路由出口:匹配的路由组件会渲染在这里 -->
    <router-view />
  </div>
</template>
<router-link> 关键属性:
  • to:目标路由(字符串 / 对象,必选);
  • replace:跳转时替换历史记录(无回退);
  • active-class:路由激活时的 CSS 类名(默认 router-link-active);
  • tag:指定渲染标签(如 tag="button",Vue 3 中可直接用 <button @click="$router.push('/')"> 替代)。

三、核心功能:动态路由与嵌套路由

1. 动态路由(路径参数)

用于匹配不确定的路由路径(如用户详情页 user/1user/2),通过 :参数名 定义动态参数。

步骤 1:定义动态路由规则
// router/index.js
const routes = [
  {
    path: '/user/:id', // :id 是动态参数(可自定义名称,如 :userId)
    name: 'User',
    component: () => import('../views/User.vue') // 懒加载组件(后续讲解)
  }
]
步骤 2:组件中获取动态参数

通过 useRoute()(Vue 3)或 $route(Vue 2)获取路由参数:

<!-- User.vue -->
<template>
  <h1>用户ID:{{ $route.params.id }}</h1> <!-- Vue 2/Vue 3 均支持 -->
</template>

<script setup>
// Vue 3 组合式 API 写法(推荐)
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.params.id) // 打印动态参数 id
</script>
关键说明:
  • 动态参数变化时(如从 user/1 跳转到 user/2),组件不会重新渲染,需通过 watch 监听参数变化:

    // Vue 3 监听参数变化
    watch(
      () => route.params.id,
      (newId, oldId) => {
        console.log('用户ID变化:', newId)
        // 执行数据刷新逻辑
      }
    )
    

2. 嵌套路由(父子路由)

用于实现页面布局嵌套(如后台管理系统的 “侧边栏 + 主内容区”),通过 children 配置子路由。

步骤 1:定义嵌套路由规则
// router/index.js
const routes = [
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: () => import('../views/Dashboard.vue'),
    // 子路由:path 不加 /,会拼接父路由路径(如 /dashboard/profile)
    children: [
      {
        path: 'profile', // 子路由路径(完整路径:/dashboard/profile)
        name: 'Profile',
        component: () => import('../views/Profile.vue')
      },
      {
        path: 'settings', // 完整路径:/dashboard/settings
        name: 'Settings',
        component: () => import('../views/Settings.vue')
      }
    ]
  }
]
步骤 2:父组件中添加子路由出口

父组件(Dashboard.vue)需包含 <router-view>,子路由组件会渲染到此处:

<!-- Dashboard.vue -->
<template>
  <div class="dashboard">
    <!-- 父组件布局(如侧边栏) -->
    <aside>
      <router-link :to="{ name: 'Profile' }">个人资料</router-link>
      <router-link :to="{ name: 'Settings' }">设置</router-link>
    </aside>
    <!-- 子路由出口:子组件渲染在这里 -->
    <main>
      <router-view />
    </main>
  </div>
</template>

四、编程式导航(替代 <router-link>

除了声明式的 <router-link>,还可通过 JavaScript 代码触发路由跳转(编程式导航),核心 API 如下:

1. 核心方法(Vue 3)

需先通过 useRouter() 获取路由实例:

import { useRouter } from 'vue-router'
const router = useRouter() // 路由实例(对应 Vue 2 的 this.$router)
方法作用示例
router.push()跳转到新路由(添加历史记录,可回退)router.push('/about') 或 router.push({ name: 'About' })
router.replace()跳转到新路由(替换当前历史记录,不可回退)router.replace('/about')
router.go(n)前进 / 后退 n 步(n 为数字,如 1 前进,-1 后退)router.go(-1)(回退上一页)
router.back()回退上一页(等价于 go(-1)router.back()
router.forward()前进一页(等价于 go(1)router.forward()
带参数跳转示例:
// 1. 动态路由带参数(path 写法)
router.push(`/user/${id}`)

// 2. 命名路由带参数(推荐,解耦 path)
router.push({
  name: 'User',
  params: { id: 123 } // 动态参数
})

// 3. 带查询参数(URL 后拼接 ?xxx=xxx)
router.push({
  path: '/search',
  query: { keyword: 'vue' } // URL 结果:/search?keyword=vue
})

2. Vue 2 差异

Vue 2 中无需 useRouter(),直接通过 this.$router 调用方法:

// Vue 2 编程式导航
this.$router.push('/about')
this.$router.push({ name: 'User', params: { id: 123 } })

五、命名路由与命名视图

1. 命名路由

给路由定义 name 属性,跳转时可通过 name 替代 path,避免硬编码路径(推荐使用):

// 路由规则
{ path: '/about', name: 'About', component: About }

// 跳转方式(推荐)
router.push({ name: 'About' })
<router-link :to="{ name: 'About' }">关于我们</router-link>

2. 命名视图

用于一个页面多个路由出口(如页面分为 header、main、footer 三个区域,分别渲染不同路由组件)。

步骤 1:定义命名视图路由规则

通过 components(复数)配置多个命名组件:

// router/index.js
const routes = [
  {
    path: '/',
    components: {
      default: Home, // 默认视图(对应无 name 的 <router-view>)
      header: Header, // 命名视图 header
      footer: Footer  // 命名视图 footer
    }
  }
]
步骤 2:页面中指定命名视图出口
<template>
  <!-- 命名视图:通过 name 属性匹配 -->
  <router-view name="header" />
  <!-- 默认视图:无 name 属性 -->
  <router-view />
  <router-view name="footer" />
</template>

六、路由守卫(权限控制核心)

路由守卫用于拦截路由跳转,实现权限验证、页面拦截、数据预处理等功能。分为 3 类:全局守卫、路由独享守卫、组件内守卫。

1. 全局守卫(影响所有路由)

在 router/index.js 中配置,对所有路由生效:

const router = createRouter({ ... })

// 1. 全局前置守卫:路由跳转前触发(常用!权限验证)
router.beforeEach((to, from, next) => {
  // to:目标路由对象(要跳转到的路由)
  // from:当前路由对象(从哪个路由跳转)
  // next:放行函数(Vue 3 中可省略,直接 return true/false)

  // 示例:未登录拦截(跳转到登录页)
  const isLogin = localStorage.getItem('token') // 模拟登录状态
  if (to.path === '/profile' && !isLogin) {
    return { name: 'Login' } // Vue 3 直接返回目标路由(替代 next('/login'))
    // Vue 2 写法:next('/login')
  }
  return true // 放行(默认放行,可省略)
})

// 2. 全局解析守卫:在 beforeRouteEnter 之后触发(少用)
router.beforeResolve((to, from) => { ... })

// 3. 全局后置守卫:路由跳转后触发(无 next,常用作页面标题修改)
router.afterEach((to, from) => {
  // 示例:修改页面标题(路由规则中可配置 meta 元信息)
  document.title = to.meta.title || '默认标题'
})
路由元信息(meta):

可在路由规则中添加 meta 字段,存储路由额外信息(如标题、权限要求):

const routes = [
  {
    path: '/profile',
    name: 'Profile',
    component: Profile,
    meta: {
      title: '个人资料', // 页面标题
      requiresAuth: true // 是否需要登录
    }
  }
]

// 全局前置守卫中使用 meta
router.beforeEach((to) => {
  if (to.meta.requiresAuth && !localStorage.getItem('token')) {
    return { name: 'Login' }
  }
})

2. 路由独享守卫(仅影响当前路由)

在单个路由规则中配置,仅对该路由生效:

const routes = [
  {
    path: '/profile',
    name: 'Profile',
    component: Profile,
    // 路由独享守卫:跳转前触发
    beforeEnter: (to, from) => {
      const isLogin = localStorage.getItem('token')
      if (!isLogin) {
        return { name: 'Login' } // 未登录拦截
      }
    }
  }
]

3. 组件内守卫(仅影响当前组件)

在路由组件内部定义,更灵活控制组件的路由行为:

<script setup>
import { onBeforeRouteEnter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'

// 1. 进入组件前触发(此时组件未创建,无法访问 this/Vue 3 响应式数据)
onBeforeRouteEnter((to, from) => {
  // 如需访问组件实例,可通过 next(vm => { ... })(Vue 2)或 return 回调(Vue 3)
  // Vue 3 写法:return (vm) => console.log(vm)
})

// 2. 路由参数变化时触发(如 /user/1 → /user/2,组件不刷新)
onBeforeRouteUpdate((to, from) => {
  console.log('参数变化:', to.params.id)
})

// 3. 离开组件前触发(常用作“未保存提示”)
onBeforeRouteLeave((to, from) => {
  const isSaved = false // 模拟是否保存数据
  if (!isSaved) {
    return confirm('数据未保存,确定离开?') // 取消则拦截跳转
  }
})
</script>

<!-- Vue 2 组件内守卫写法(选项式 API) -->
<script>
export default {
  beforeRouteEnter(to, from, next) { ... },
  beforeRouteUpdate(to, from, next) { ... },
  beforeRouteLeave(to, from, next) { ... }
}
</script>

七、路由懒加载(性能优化)

默认情况下,所有路由组件会在应用启动时一次性加载,导致首屏加载缓慢。路由懒加载可实现组件按需加载(跳转时才加载组件),优化首屏性能。

实现方式:动态 import 语法

将路由规则中的 component 改为动态导入函数(() => import('组件路径')):

// router/index.js
const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/Home.vue') // 懒加载
  },
  {
    path: '/about',
    name: 'About',
    // 分块命名(打包后生成 about.[hash].js,方便调试)
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]
原理:
  • 静态 import:编译时加载组件,打包到主文件;
  • 动态 import():运行时加载组件,打包为独立的 chunk 文件,跳转时才请求该文件。

八、404 页面配置(路由匹配失败)

配置通配符路由(* 或 /:pathMatch(.*)*),匹配所有未定义的路由,跳转到 404 页面:

// router/index.js
const routes = [
  // 其他路由规则...
  {
    // Vue 3 写法:匹配所有路径(需放在最后!)
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: () => import('../views/NotFound.vue')
  },
  // Vue 2 写法:path: '*'
]
注意:
  • 404 路由需放在所有路由规则的最后(路由匹配按顺序执行,先匹配先生效);
  • /:pathMatch(.*)* 中的 * 表示 “捕获所有参数”,可通过 $route.params.pathMatch 获取错误路径。

九、常见问题与解决方案

1. 路由跳转后页面不刷新?

  • 原因:动态路由参数变化(如 /user/1 → /user/2)时,组件不会重新创建;
  • 解决方案:监听 $route 变化或使用 onBeforeRouteUpdate 守卫。

2. History 模式下刷新页面 404?

  • 原因:History 模式依赖 HTML5 pushState,刷新时浏览器会请求后端接口,后端无对应路由则返回 404;
  • 解决方案:后端配置所有路由指向 index.html(如 Nginx、Apache 配置)。

3. 路由守卫中 next 调用错误导致死循环?

  • 原因:Vue 2 中 next() 调用多次或条件判断缺失;
  • 解决方案:Vue 3 中直接 return 目标路由 /true/false,无需 next;Vue 2 确保每个分支都有 next()

4. <router-link> 激活样式不生效?

  • 原因:active-class 配置错误或路由路径不匹配;
  • 解决方案:使用 exact 属性(Vue 2)或 exact-active-class(精确匹配激活样式)。

十、总结

Vue Router 的核心流程:

  1. 定义路由规则(routes)→ 创建路由实例(createRouter)→ 挂载到 Vue 应用;
  2. 通过 <router-link> 或编程式导航触发路由跳转;
  3. 路由守卫拦截并处理(权限验证等);
  4. 匹配的路由组件渲染到 <router-view>

核心功能优先级:基础配置→动态路由→嵌套路由→路由守卫→懒加载,掌握这些即可满足绝大多数 SPA 开发需求。如需更复杂场景(如路由过渡动画、路由元信息扩展),可参考 Vue Router 官方文档

若有错误或描述不当的地方,烦请评论或私信指正,万分感谢 😃

如果我的文章对你有帮助,你的赞👍就是对我的最大支持^_^