路由的基本概念
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 API(pushState()、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(如 location、history),完全由自定义逻辑控制,适合无 URL 需求的场景(如 Electron 桌面应用、弹窗内路由、多标签页内嵌视图等)。
核心实现思路
内存路由的实现围绕 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 2 | vue-router@3 | npm i vue-router@3 --save | new Router()、$router/$route |
| Vue 3 | vue-router@4 | npm i vue-router@4 --save | createRouter()、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/1、user/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 的核心流程:
- 定义路由规则(
routes)→ 创建路由实例(createRouter)→ 挂载到 Vue 应用; - 通过
<router-link>或编程式导航触发路由跳转; - 路由守卫拦截并处理(权限验证等);
- 匹配的路由组件渲染到
<router-view>。
核心功能优先级:基础配置→动态路由→嵌套路由→路由守卫→懒加载,掌握这些即可满足绝大多数 SPA 开发需求。如需更复杂场景(如路由过渡动画、路由元信息扩展),可参考 Vue Router 官方文档。
若有错误或描述不当的地方,烦请评论或私信指正,万分感谢 😃
如果我的文章对你有帮助,你的赞👍就是对我的最大支持^_^