什么是vue路由
Vue 路由就是让你可以在 单页应用(SPA)中通过路径切换不同的“页面” 。
在传统网页中,点击链接会刷新整个页面。而在 Vue 中,通过路由机制,可以点击菜单或链接切换视图,而页面不会整体刷新,体验更流畅。
核心概念:
路由表(routes)
定义 URL 和组件的对应关系。例如:
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
创建路由实例
使用 createRouter
+ createWebHistory
创建路由对象:
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes, // 路由表
})
路由实例(即 const router = createRouter({...})
创建出来的对象)是 Vue 应用中管理“页面切换”和“路由逻辑”的核心对象,它的作用就像一个“导航控制中心”。
路由实例的主要作用:
- 存储路由表(路径和页面组件的对应关系)
const router = createRouter({
history: createWebHistory(),
routes: [ /* 路由表 */ ]
})
你定义的所有页面路径和组件都会交给这个实例管理。
- 处理页面跳转(编程式导航)
比如你写:
router.push('/login')
就是通过路由实例来跳转到 /login
页面。
- 提供导航守卫(路由拦截)
router.beforeEach((to, from, next) => {
// 比如没登录就不让进
if (!isLogin && to.path !== '/login') next('/login')
else next()
})
这就是权限控制,必须通过路由实例注册。
- 集成到vue应用里
app.use(router)
- 支持页面动画、过渡、嵌套路由等功能
你在模板中用 <router-view>
和 <router-link>
的背后,都是这个实例在工作。
路由实例 = Vue 项目的“导航大脑”
- 一个 Vue 应用只有一个 Vue 实例,就是你在
main.js
中通过createApp(App)
创建的那一个 - 它知道你有哪些页面
- 它知道当前处在哪个页面
- 它负责把页面切换的过程做得顺滑、可控、支持权限等
一个简单的vue3路由实例完整示例
项目结构:
src/
├─ main.js
├─ App.vue
├─ router/
│ └─ index.js
├─ views/
│ ├─ Home.vue
│ └─ About.vue
views/Home.vue
<template>
<div>
<h1>首页</h1>
<router-link to="/about">去关于页</router-link>
</div>
</template>
views/About.vue
<template>
<div>
<h1>关于我们</h1>
<router-link to="/">回到首页</router-link>
</div>
</template>
router/index.js(创建路由实例)
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
App.vue
(放置 <router-view>
)
<template>
<div id="app">
<router-view />
</div>
</template>
main.js
(创建 Vue 应用 + 使用路由)
import { createApp } from 'vue'
import App from './App.vue'
import router from './router' // 引入我们刚才创建的路由实例
const app = createApp(App)
app.use(router) // 安装路由
app.mount('#app') // 挂载应用
运行后你将看到:
-
打开
/
显示首页; -
点“去关于页”跳到
/about
; -
路由切换 不会刷新页面,体验非常流畅。
router-view
显示当前路由对应的页面组件
简单理解:
你在路由配置(routes
)中定义了路径和组件的对应关系,比如:
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
当用户访问 /about
,你希望在页面上展示 About
组件,这时候就需要一个“容器”来展示它 —— router-view
就是这个“容器”。
<template>
<div id="app">
<Navbar />
<router-view /> <!-- 这里会根据路由显示不同的页面 -->
</div>
</template>
访问 /about
时,相当于:
<router-view /> ⟶ 替换成 <About />
访问 /
时:
<router-view /> ⟶ 替换成 <Home />
router-view
是 Vue 应用中用于“切换页面”的插槽位,它根据地址栏的路径,动态地把相应的组件渲染进去。
项目中的router.js
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes,
// 刷新时,滚动条位置还原
scrollBehavior: () => ({ left: 0, top: 0 }),
});
createWebHashHistory()
:Vue Router 提供的一种路由模式,称为 哈希模式- 路由地址会带
#
,比如:
http://localhost:5000/#/login
- 浏览器不会向服务器真正发起
/login
请求(更安全、不易 404)
路由模式(history)
哈希模式(createWebHashHistory):使用 location.hash
(#)控制路由,不会向服务器发请求,最稳妥
- URL 带有
#
,如:http://example.com/#/login
- 浏览器只请求
http://example.com
,#/login
部分不会发送给服务器 - 不依赖后端,任何服务器都能跑
历史模式(createWebHistory):使用 history.pushState()
控制路由,URL 更干净,需要服务器支持
- 没有
#
,URL 看起来更“正常”http://example.com/login
- 使用浏览器的
history.pushState()
API - 需要后端服务器支持
- 适合场景:部署在有后端的服务器,后端能配合支持 SPA 路由重定向,你追求干净的 URL(无 #)
你可以理解为:Vue Router 的路由模式 ≈ 浏览器中地址栏路径的处理方式
- 它决定了:当你访问一个地址时,Vue 应该如何解析、是否刷新页面、是否和服务器交互
为什么createWebHistory()需要后端支持
使用 createWebHistory()
时,Vue 会让浏览器地址栏变成这种格式:
http://example.com/user/profile
这看起来像一个标准的 URL,对吧?但浏览器认为你是要向服务器请求 /user/profile
这个资源。
问题来了:
如果你访问这个 URL,然后刷新页面,浏览器会直接向服务器请求 /user/profile
路径下的文件。
但是这个路径其实只存在于前端路由里,后端并没有这个页面,于是就返回了 404。
解决方式:让服务器“兜底”返回 index.html
前端路由是 SPA(单页应用),你访问任何路径其实都应该返回 index.html
,再由前端 router 接管。
需要后端配置类似这样的规则:
# 以 nginx 为例
location / {
try_files $uri $uri/ /index.html;
}
这句的意思是:
如果访问的资源不存在,就统一返回
/index.html
浏览器在刷新页面或直接输入 URL 时,请求的是服务器,而不是前端程序
举个例子:
假设你项目用了 createWebHistory()
,前端定义了一个路由 /user
,然后你直接访问:
http://example.com/user
浏览器行为:
-
浏览器先向服务器发请求:
GET /user
-
服务器如果找不到
/user
这个路径,就会直接返回 404 -
此时前端根本没有机会运行代码,自然也就没法兜底
在本地开发时,Vue 项目其实是借助像 Vite 或 Webpack Dev Server 这样的“开发服务器”来实现的,它们帮你自动处理了前面提到的路由兜底问题。
例如vite,vite.config.js
中已经帮你配置好了本地开发服务器:
export default defineConfig({
server: {
host: '0.0.0.0',
port: 5000,
proxy: { /* ... */ }
}
})
当你运行 npm run dev
或 vite
时:
-
Vite 启动一个本地开发服务器(比如 http://localhost:5000)
-
浏览器访问
/user
时,Vite 自动兜底返回index.html
-
Vue Router 接管路由,匹配到
/user
并渲染对应组件 -
你可以自由刷新、跳转,完全不担心 404 问题
为什么 createWebHashHistory()
不需要后端兜底?
因为 hash 模式的路径像这样:
http://example.com/#/user
#
号后面的部分(也就是#/user
)不会被服务器处理;- 浏览器只请求
/
,返回index.html
; - 然后前端的 Vue Router 再解析
#/user
路由,显示对应页面。
routes(路由配置表)
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes,
// 刷新时,滚动条位置还原
scrollBehavior: () => ({ left: 0, top: 0 }),
});
其中,routes是路由配置表,它的作用是告诉路由器:不同的路径要加载哪个组件
routes
是什么?
routes
是一个数组,每个元素都是一个“路由对象” ,定义了“URL路径”和“要渲染的组件”之间的映射关系。简单理解就是:
const routes = [
{
path: '/', // URL路径,比如 http://localhost:3000/
component: HomePage, // 对应要渲染的 Vue 组件
},
{
path: '/about',
component: AboutPage,
},
];
用法场景
比如你的 SPA(单页应用)有两个页面:
- 首页
/
显示首页组件<HomePage>
- 关于页
/about
显示<AboutPage>
你就要这样写:
import HomePage from './views/HomePage.vue';
import AboutPage from './views/AboutPage.vue';
const routes = [
{ path: '/', component: HomePage },
{ path: '/about', component: AboutPage },
];
然后在你提供的 createRouter
中使用:
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes, // 这里把 routes 配置表传进来
scrollBehavior: () => ({ left: 0, top: 0 }),
});
路由配置
静态路由
const constantRoutes = [ {
path: "/",
name: "Layout",
component: Layout,
redirect: "/dashboard",
children: [
{
path: "/dashboard",
component: () => import("@/views/dashboard/index.vue"),
name: "dashboard",
meta: {
title: "工作台",
icon: "HomeFilled",
requiresAuth: true,
cache: true,
perms: [
"/index"
]
},
{
……
}
]
} ]
静态路由(Static Routes) :在项目初始化时就写死在代码中的路由,页面不管是否有权限都能访问或展示。
顶层路由:Layout:
path: "/"
:当用户访问根路径时,会加载这个路由component: Layout
:加载的是Layout
组件(通常是一个包含顶部导航、侧边栏、内容区域的整体布局组件)。redirect: "/dashboard"
:默认跳转到/dashboard
。children
:配置类一个子路由/dashboard
,用于在 Layout 中展示实际内容。
设置component后为什么还要设置redirect
redirect
是为了默认显示一个子页面
访问 /
的时候虽然加载了 Layout
组件,但这个组件里还有 <router-view>
,它需要显示哪个子页面呢
如果没有 redirect
,你访问 /
时:
- 只加载了
Layout
外壳(导航栏/侧边栏等), <router-view>
是空的,因为没有指定默认子路由。
Vue Router 的嵌套路由设计本身就是:父子路由共存,子路由内容显示在父组件的 <router-view>
中
当你设置了父路由
Layout
+redirect: /dashboard
,跳转后仍然在Layout
里面显示子页面,所以不会“覆盖”父路由,而是“填充”它的router-view
。
meta
children: [
{
path: "/dashboard",
component: () => import("@/views/dashboard/index.vue"),
name: "dashboard",
meta: {
title: "工作台",
icon: "HomeFilled",
requiresAuth: true,
cache: true,
perms: [
"/index"
]
}
在 Vue Router 中,meta
是路由记录中的一个可选字段,用于存放自定义的附加信息。
meta
不影响路由匹配和跳转逻辑,但它的字段可以被开发者在组件/导航守卫中使用。
字段 | 类型 | 作用 |
---|---|---|
title | string | 用于显示在菜单、标签页、面包屑等 |
icon | string | 用于配合菜单显示图标 |
requiresAuth | boolean | 是否需要登录才能访问 |
roles | string[] | 哪些用户角色可以访问(如:['admin']) |
perms | string[] | 权限字符串标识(如 ['/user/list']) |
cache | boolean | 是否使用 <keep-alive> 缓存组件 |
hidden | boolean | 是否在菜单中隐藏此项 |
affix | boolean | 是否固定在多标签页导航栏中 |
breadcrumb | boolean | 是否显示在面包屑导航中 |
实际开发中的用法举例:
- 菜单显示
在生成侧边栏菜单时:
// 只显示有 meta.title 的路由
const menus = routes.filter(r => r.meta && !r.meta.hidden) //过滤路由数组中的“可显示在菜单上的项”
.map(r => ({//把每个符合条件的路由对象映射成菜单需要的格式
title: r.meta.title,
icon: r.meta.icon,
path: r.path
}));
假设你的路由长这样:
const routes = [
{
path: "/dashboard",
component: Dashboard,
meta: { title: "工作台", icon: "HomeFilled" }
},
{
path: "/user",
component: User,
meta: { title: "用户管理", icon: "User", hidden: true }
},
{
path: "/settings",
component: Settings,
meta: { title: "系统设置", icon: "Setting" }
}
];
经过处理后的menus就是:
[
{ title: "工作台", icon: "HomeFilled", path: "/dashboard" },
{ title: "系统设置", icon: "Setting", path: "/settings" }
]
// 注意:"/user" 被 hidden 掉了,不在菜单中显示
最终生成了一个可以直接用于菜单渲染的数组
- 权限控制(登录校验)
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isLoggedIn()) {
next("/login");
} else {
next();
}
});
-
router.beforeEach:vue router的全局前置守卫,它会在每次路由跳转之前执行:
router.beforeEach((to, from, next) => { // to: 即将进入的路由 // from: 当前离开的路由 // next: 控制是否跳转,怎么跳 });
-
to.meta.requiresAuth
:检查目标路由是否需要登录(在路由配置中设置了requiresAuth: true
) -
!isLoggedIn()
:假设你定义了这个函数,用来判断用户当前是否已登录 -
next("/login")
:重定向用户到/login
页面,防止未授权访问受保护的页面 -
next()
:如果目标页面不需要登录,或者用户已经登录了,就正常放行路由跳转
当访问需要登录的页面,且用户没登录时,就执行 next("/login")
- 权限控制(角色/权限)
if (to.meta.perms && !userHasAnyPerm(to.meta.perms)) {
return next("/403");
}
to.meta.perms
:to
是当前目标路由对象,meta.perms
是你在路由中自定义的权限列表(数组)
例如路由配置可能长这样:
{
path: "/user/list",
component: () => import("@/views/user/list.vue"),
meta: {
title: "用户列表",
perms: ["/user/list"]
}
}
- userHasAnyPerm(to.meta.perms):自定义函数,判断当前用户是否拥有这些权限
通常会去检查当前登录用户的权限集合,比如:
function userHasAnyPerm(requiredPerms) {
const userPerms = getUserPermsFromStoreOrToken(); // 假设返回:['/user/list', '/dashboard']
return requiredPerms.some(perm => userPerms.includes(perm));
}
- 页面缓存控制
在 <keep-alive>
中控制是否缓存:
<keep-alive :include="cachedPages">
<router-view />
</keep-alive>
- keep-alive:用于缓存组件的状态,比如保留页面滚动位置、表单数据等。
router-view
是用来渲染路由匹配到的组件cachedPages
是一个数组,告诉<keep-alive>
哪些组件要被缓存
在代码中收集:
const cachedPages = routes
.filter(r => r.meta?.cache)
.map(r => r.name);
r => r.meta?.cache
:如果r.meta
存在,就访问r.meta.cache
;如果r.meta
不存在,返回undefined
而不是报错。
你可以把meta理解为:路由的“标签”或“注解”,用来自定义控制页面行为
而你定义的所有字段,只要项目代码里能识别使用,就都是合法的。