vue路由

14 阅读10分钟

什么是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 应用中管理“页面切换”和“路由逻辑”的核心对象,它的作用就像一个“导航控制中心”。

路由实例的主要作用:

  1. 存储路由表(路径和页面组件的对应关系)
const router = createRouter({
  history: createWebHistory(),
  routes: [ /* 路由表 */ ]
})

你定义的所有页面路径和组件都会交给这个实例管理。

  1. 处理页面跳转(编程式导航)

比如你写:

router.push('/login')

就是通过路由实例来跳转到 /login 页面。

  1. 提供导航守卫(路由拦截)
router.beforeEach((to, from, next) => {
  // 比如没登录就不让进
  if (!isLogin && to.path !== '/login') next('/login')
  else next()
})

这就是权限控制,必须通过路由实例注册。

  1. 集成到vue应用里
app.use(router)

  1. 支持页面动画、过渡、嵌套路由等功能

你在模板中用 <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 devvite 时:

  • 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 不影响路由匹配和跳转逻辑,但它的字段可以被开发者在组件/导航守卫中使用。

字段类型作用
titlestring用于显示在菜单、标签页、面包屑等
iconstring用于配合菜单显示图标
requiresAuthboolean是否需要登录才能访问
rolesstring[]哪些用户角色可以访问(如:['admin'])
permsstring[]权限字符串标识(如 ['/user/list'])
cacheboolean是否使用 <keep-alive> 缓存组件
hiddenboolean是否在菜单中隐藏此项
affixboolean是否固定在多标签页导航栏中
breadcrumbboolean是否显示在面包屑导航中

实际开发中的用法举例:

  1. 菜单显示

在生成侧边栏菜单时:

// 只显示有 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 掉了,不在菜单中显示

最终生成了一个可以直接用于菜单渲染的数组

  1. 权限控制(登录校验)
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")

  1. 权限控制(角色/权限)
if (to.meta.perms && !userHasAnyPerm(to.meta.perms)) {
  return next("/403");
}
  • to.meta.permsto 是当前目标路由对象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));
}
  1. 页面缓存控制

<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理解为:路由的“标签”或“注解”,用来自定义控制页面行为

而你定义的所有字段,只要项目代码里能识别使用,就都是合法的。