Vue Router完全指南 —— 从基础配置到权限控制

前面两篇我分为讲了vue的一些基本和常用的语法,现在这篇文章带你了解vue的路由管理是怎么实现的

引言

在 Vue 开发单页应用(SPA)时,"页面跳转不刷新、URL 与组件联动" 是核心需求 ------ 而 Vue Router 就是实现这一需求的官方工具。它通过管理 "路径(path)与组件(component)" 的对应关系,让我们能像浏览多页网站一样切换内容,却始终保持单页应用的流畅性。本文将从基础配置入手,一步步解锁路由导航、传参、嵌套、守卫等进阶能力,帮你搞定任何 Vue 项目的路由管理。

一、先搞懂:Vue Router 核心概念

在写代码前,我们得先理清几个关键概念,避免后续混淆。

1.1 什么是路由?

**路由本质是一组 key-value 的对应关系。**在前端路由中:

  • key:URL 路径(如/home
  • value:对应渲染的组件(如Home.vue

多个路由需要通过 "路由器" 统一管理,Vue Router 就是这个 "路由器",负责监听 URL 变化,渲染对应的组件。

1.2 SPA 应用的特点

Vue Router 服务于 SPA(单页 Web 应用),这类应用有 4 个核心特点:

  1. 整个应用只有 1 个完整 HTML 页面(index.html);
  2. 点击导航不刷新页面,只做局部内容更新
  3. URL 变化通过前端路由控制,而非后端跳转;
  4. 数据需通过 Ajax/axios 异步获取。

1.3 路由项目目录结构

一个标准的 Vue+Vue Router 项目结构如下(重点关注router目录):

python 复制代码
src/
├── router/          # 路由配置目录
│   └── index.ts     # 路由规则与路由器创建
├── views/           # 路由页面组件(如Home.vue、About.vue)
├── components/      # 通用功能组件
├── App.vue          # 根组件(放导航与路由出口)
└── main.ts          # 入口文件(注册路由器)

二、入门:Vue Router 4 基础使用

从安装到实现第一个路由跳转,只需 4 步,全程可复制使用。

2.1 步骤 1:安装 Vue Router 4

Vue 3 对应的路由版本是 Vue Router 4,通过 npm 安装:

bash 复制代码
npm install vue-router@4

2.2 步骤 2:创建路由器

src/router/index.ts中,用createRouter创建路由器,定义路由规则(routes):

TypeScript 复制代码
// src/router/index.ts
import { createRouter, createWebHistory } from "vue-router";
// 导入路由页面组件
import Home from "@/views/Home.vue";
import News from "@/views/News.vue";
import About from "@/views/About.vue";

// 1. 定义路由规则:path -> component的对应关系
const routes = [
  {
    path: "/home",    // URL路径
    name: "home",     // 命名路由(可选,用于简化跳转)
    component: Home   // 对应组件
  },
  {
    path: "/news",
    name: "news",
    component: News
  },
  {
    path: "/about",
    name: "about",
    component: About
  }
];

// 2. 创建路由器
const router = createRouter({
  history: createWebHistory(), // 路由模式(history模式,无#)
  routes                       // 传入路由规则
});

// 3. 导出路由器,供main.ts注册
export default router;

2.3 步骤 3:注册路由器

在入口文件src/main.ts中,通过app.use(router)注册路由器,让整个应用支持路由

TypeScript 复制代码
// src/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router"; // 导入路由器

const app = createApp(App);
app.use(router); // 注册路由器
app.mount("#app");

2.4 步骤 4:使用路由(导航 + 展示)

在根组件App.vue中,用两个核心组件实现路由功能:

  • RouterLink:替代<a>标签的导航组件,点击不刷新页面;
  • RouterView:路由组件的 "容器",URL 匹配的组件会渲染在这里。

代码示例:

html 复制代码
<template>
  <div class="app">
    <h1>Vue Router 测试</h1>
    <!-- 1. 导航区:用RouterLink实现跳转 -->
    <div class="nav">
      <!-- active-class:跳转后添加的激活样式 -->
      <RouterLink to="/home" active-class="active">首页</RouterLink>
      <RouterLink to="/news" active-class="active">新闻</RouterLink>
      <RouterLink to="/about" active-class="active">关于</RouterLink>
    </div>
    <!-- 2. 展示区:RouterView渲染匹配的组件 -->
    <div class="content">
      <RouterView></RouterView>
    </div>
  </div>
</template>

<script setup lang="ts" name="App">
// 导入RouterLink和RouterView(setup语法糖需手动导入)
import { RouterLink, RouterView } from "vue-router";
</script>

<style scoped>
.nav { margin: 20px 0; }
.nav a { margin-right: 20px; text-decoration: none; }
.active { color: #42b983; font-weight: bold; } /* 激活样式 */
</style>

三、核心配置:路由模式与跳转方式

这部分解决两个常见问题:"URL 带不带 #?""怎么简化跳转写法?"

3.1 路由模式:history vs hash

Vue Router 提供两种 URL 模式,核心区别是 URL 是否带#,需在创建路由器时配置:

模式 配置代码 优点 缺点
history 模式 history: createWebHistory() URL 无 #,美观,接近传统网站 上线需服务端配置(否则刷新 404)
hash 模式 history: createWebHashHistory() 兼容性好,无需服务端处理 URL 带 #,SEO 优化较差

注意 :history 模式需服务端配置(如 Nginx 转发所有请求到index.html),否则用户刷新非首页会报 404;hash 模式无需额外配置,适合快速原型或兼容性要求高的场景。

3.2 路由跳转:to 的两种写法

RouterLinkto属性支持两种写法,可根据场景选择:

写法 1:字符串写法(简单场景)

直接写完整路径,适合无参数的跳转:

html 复制代码
<!-- 字符串写法:直接指定路径 -->
<RouterLink to="/home">首页</RouterLink>
<!-- 带query参数的字符串写法(后面讲传参时详细说) -->
<RouterLink to="/news/detail?id=1&title=新闻标题">新闻详情</RouterLink>
写法 2:对象写法(复杂场景)

当需要传参数或用 "命名路由" 简化路径时,用对象写法更灵活:

html 复制代码
<!-- 1. 仅指定路径 -->
<RouterLink :to="{ path: '/home' }">首页</RouterLink>

<!-- 2. 用命名路由(需先给路由配置name) -->
<RouterLink :to="{ name: 'home' }">首页</RouterLink> 
<!-- 优势:无需写完整路径,路径变了也不用改这里 -->

<!-- 3. 带参数(后面传参章节详解) -->
<RouterLink :to="{ 
  name: 'newsDetail', 
  params: { id: 1, title: '新闻标题' } 
}">新闻详情</RouterLink>

3.3 命名路由:简化跳转的 "快捷键"

给路由规则配置name属性(命名路由),可替代完整路径,减少路径修改时的工作量。

步骤 1:给路由配置 name
TypeScript 复制代码
// src/router/index.ts
const routes = [
  {
    name: "home",     // 命名路由:name唯一,建议语义化
    path: "/home",
    component: Home
  },
  {
    name: "newsDetail", // 新闻详情的命名路由
    path: "/news/detail/:id/:title", // 带params占位(后面讲)
    component: NewsDetail
  }
];
步骤 2:用 name 跳转
html 复制代码
<!-- 无需写完整路径,用name即可 -->
<RouterLink :to="{ name: 'newsDetail', params: { id: 1, title: '标题' } }">
  新闻详情
</RouterLink>

优势 :若后续/news/detail路径改成/news/content,只需修改路由配置的path,所有用name跳转的代码无需改动,降低维护成本。

四、进阶:嵌套路由与路由传参

这部分解决 "页面内嵌套子组件"(如新闻列表→新闻详情)和 "页面间传递数据"(如列表→详情传 ID)的核心需求。

4.1 嵌套路由:实现 "父组件 - 子组件" 联动

场景:新闻页面(News.vue)中,左侧是新闻列表,右侧是新闻详情,点击列表项切换详情 ------ 这就需要嵌套路由(父路由/news,子路由/news/detail)。

步骤 1:配置嵌套路由(children)

在父路由(如/news)中用children配置子路由,子路由的path无需加/(会自动拼接父路由路径):

TypeScript 复制代码
// src/router/index.ts
const routes = [
  {
    name: "news",
    path: "/news",
    component: News, // 父组件:新闻列表页
    // 子路由配置:children是数组,每个元素是子路由规则
    children: [
      {
        name: "newsDetail",
        path: "detail/:id/:title", // 子路由path:无需加/,拼接后是/news/detail
        component: NewsDetail // 子组件:新闻详情页
      }
    ]
  }
];
步骤 2:父组件中预留 "子路由出口"

父组件(News.vue)中需添加RouterView,子组件会渲染在这里:

html 复制代码
<!-- News.vue(父组件) -->
<template>
  <div class="news-page">
    <!-- 左侧:新闻列表(父组件内容) -->
    <div class="news-list">
      <RouterLink 
        v-for="item in newsList" 
        :key="item.id"
        :to="{ 
          name: 'newsDetail', 
          params: { id: item.id, title: item.title } 
        }"
      >
        {{ item.title }}
      </RouterLink>
    </div>
    <!-- 右侧:子路由出口(子组件NewsDetail会渲染在这里) -->
    <div class="news-detail">
      <RouterView></RouterView>
    </div>
  </div>
</template>

<script setup lang="ts" name="News">
import { ref } from "vue";
import { RouterLink, RouterView } from "vue-router";

// 模拟新闻列表数据
const newsList = ref([
  { id: 1, title: "Vue Router 4 新特性" },
  { id: 2, title: "SPA应用路由最佳实践" }
]);
</script>

4.2 路由传参:query vs params 两种方式

页面间传递数据(如列表→详情传 ID)是路由核心需求,Vue Router 提供两种传参方式,适用场景不同。

方式 1:query 参数(URL 可见,可选参数)

query 参数会拼在 URL 后面(如/news/detail?id=1&title=标题),类似 GET 请求参数,适合传递 "可选参数"(如筛选条件、分页)。

步骤 1:传递 query 参数
html 复制代码
<!-- 写法1:字符串写法(简单) -->
<RouterLink to="/news/detail?id=1&title=Vue Router 教程">新闻详情</RouterLink>

<!-- 写法2:对象写法(推荐,参数清晰) -->
<RouterLink :to="{ 
  path: '/news/detail', // 或用name: 'newsDetail'
  query: { 
    id: 1, 
    title: 'Vue Router 教程' 
  } 
}">新闻详情</RouterLink>
步骤 2:接收 query 参数

在子组件(NewsDetail.vue)中,用useRoute钩子获取路由信息,从route.query中取参数:

html 复制代码
<!-- NewsDetail.vue -->
<script setup lang="ts" name="NewsDetail">
import { useRoute } from "vue-router";

// 1. 获取路由实例(包含参数、路径等信息)
const route = useRoute();

// 2. 接收query参数(注意:参数类型都是字符串)
const newsId = route.query.id; // "1"
const newsTitle = route.query.title; // "Vue Router 教程"

console.log("新闻ID:", newsId);
</script>
方式 2:params 参数(URL 占位,必需参数)

params 参数通过 "路径占位" 传递(如/news/detail/1/标题),URL 中不显示参数名,适合传递 "必需参数"(如资源 ID,符合 RESTful 风格)。

步骤 1:路由规则中 "占位"

先在子路由的path中用:参数名占位,声明需要接收的 params 参数:

TypeScript 复制代码
// src/router/index.ts
const routes = [
  {
    name: "news",
    path: "/news",
    component: News,
    children: [
      {
        name: "newsDetail",
        // 占位::id和:title对应params的两个参数
        path: "detail/:id/:title", 
        component: NewsDetail
      }
    ]
  }
];
步骤 2:传递 params 参数
html 复制代码
<!-- 写法1:字符串写法(需按占位顺序传) -->
<RouterLink :to="`/news/detail/${item.id}/${item.title}`">
  {{ item.title }}
</RouterLink>

<!-- 写法2:对象写法(必需用name,不能用path!) -->
<RouterLink :to="{ 
  name: 'newsDetail', // 注意:params传参不能用path
  params: { 
    id: item.id, 
    title: item.title 
  } 
}">
  {{ item.title }}
</RouterLink>
步骤 3:接收 params 参数

与 query 类似,从route.params中取参数:

html 复制代码
<script setup lang="ts" name="NewsDetail">
import { useRoute } from "vue-router";

const route = useRoute();

// 接收params参数(类型也是字符串)
const newsId = route.params.id; // "1"
const newsTitle = route.params.title; // "Vue Router 教程"
</script>
query vs params 核心对比
对比维度 query 参数 params 参数
URL 显示 ?id=1&title=标题 /1/标题(路径占位)
路由配置 无需额外配置 需在 path 中占位(:id/:title
跳转方式 支持 path/name 仅支持 name(对象写法)
参数必要性 可选(可不传) 必需(不传会导致路径错误)
适用场景 筛选条件、分页、可选参数 资源 ID、必需参数(RESTful)

五、优化:路由 props 与编程式导航

这部分解决 "组件解耦" 和 "非点击触发跳转"(如登录成功后跳转)的需求。

5.1 路由 props:让组件 "少依赖路由实例"

默认情况下,组件需通过route.query/route.params取参数,导致组件与路由强耦合(脱离路由就用不了)。用props配置可将路由参数 "注入" 为组件的 props,让组件更通用。

Vue Router 的props支持三种写法,覆盖不同场景:

写法 1:布尔值写法(仅 params 参数)

props: true时,路由的 params 参数会自动转为组件的 props:

TypeScript 复制代码
// 1. 路由配置:props: true
const routes = [
  {
    name: "newsDetail",
    path: "detail/:id/:title",
    component: NewsDetail,
    props: true // 开启props,params参数转为组件props
  }
];

// 2. 组件接收:用defineProps
<script setup lang="ts" name="NewsDetail">
// 直接接收props,无需依赖useRoute
defineProps(["id", "title"]);

// 使用时直接用id/title,不用route.params
console.log("新闻ID:", id); // 无需.route.params.id
</script>
写法 2:对象写法(固定参数)

传递固定的静态参数,适合参数不依赖路由的场景:

TypeScript 复制代码
// 路由配置:props是对象
const routes = [
  {
    path: "/user",
    component: User,
    props: { role: "guest", status: "active" } // 固定参数
  }
];

// 组件接收
<script setup lang="ts" name="User">
defineProps(["role", "status"]);
console.log(role); // "guest"
</script>
写法 3:函数写法(灵活处理参数)

支持自定义参数逻辑,可整合 query/params 参数,或对参数加工:

TypeScript 复制代码
// 路由配置:props是函数,接收route参数
const routes = [
  {
    path: "/news/detail",
    component: NewsDetail,
    // 函数返回的对象会作为props传给组件
    props: (route) => ({
      id: route.query.id, // 取query参数
      title: route.query.title,
      time: new Date().toLocaleString() // 额外加动态参数
    })
  }
];

// 组件接收
<script setup lang="ts" name="NewsDetail">
defineProps(["id", "title", "time"]);
console.log(time); // 当前时间
</script>

优势 :组件无需导入useRoute,脱离路由环境也能通过 props 传参使用(如单元测试、组件复用)。

5.2 编程式导航:非点击触发跳转

除了RouterLink(声明式导航),还可通过代码触发跳转(如登录成功后、接口请求成功后),这就是编程式导航,核心是useRouter钩子。

步骤 1:导入 useRouter 与 useRoute
html 复制代码
<script setup lang="ts">
import { useRouter, useRoute } from "vue-router";

const router = useRouter(); // 用于跳转(控制路由)
const route = useRoute();   // 用于获取当前路由信息(参数、路径等)
</script>
步骤 2:常用编程式导航方法
TypeScript 复制代码
// 1. 跳转到指定路径(字符串写法)
router.push("/home");

// 2. 跳转到指定路径(对象写法,带query参数)
router.push({
  path: "/news/detail",
  query: { id: 1, title: "标题" }
});

// 3. 用命名路由跳转(带params参数)
router.push({
  name: "newsDetail",
  params: { id: 1, title: "标题" }
});

// 4. 替换当前历史记录(不会新增历史条目,后退不会回到当前页)
// 场景:登录页→首页,后退不想回到登录页
router.replace("/home");

// 5. 后退/前进(类似浏览器的前进后退按钮)
router.go(-1); // 后退1步
router.go(1);  // 前进1步
router.back(); // 等价于go(-1)
router.forward(); // 等价于go(1)

六、异常处理:重定向与 404 页面

处理 "默认首页" 和 "无效路径" 的场景,提升用户体验。

6.1 重定向:默认路径跳转

当用户访问根路径(/)时,自动跳转到首页(/home),用redirect配置:

TypeScript 复制代码
// src/router/index.ts
const routes = [
  // 重定向:访问/时,自动跳转到/home
  { path: "/", redirect: "/home" },
  
  // 其他路由规则
  { path: "/home", component: Home },
  { path: "/about", component: About }
];

也支持重定向到命名路由:

TypeScript 复制代码
{ path: "/", redirect: { name: "home" } }

6.2 404 页面:无效路径处理

当用户访问不存在的路径时,显示 404 页面,需用 "通配符" 匹配所有未定义的路径。

步骤 1:创建 404 组件(NotFound.vue)
html 复制代码
<!-- src/views/NotFound.vue -->
<template>
  <div class="not-found">
    <h1>404</h1>
    <p>页面不存在</p>
    <RouterLink to="/home">返回首页</RouterLink>
  </div>
</template>
步骤 2:配置通配符路由

通配符/:pathMatch(.*)*会匹配所有未定义的路径,需放在路由规则的最后面(路由匹配按顺序执行):

TypeScript 复制代码
// src/router/index.ts
import NotFound from "@/views/NotFound.vue";

const routes = [
  // 其他路由规则...
  
  // 404页面:放在最后面
  {
    path: "/:pathMatch(.*)*", // 通配符,匹配所有未定义路径
    name: "NotFound",
    component: NotFound
  }
];

七、权限控制:路由守卫的核心应用

路由守卫是 Vue Router 的 "安全门",可在路由跳转前 / 后执行逻辑(如登录验证、角色权限判断),核心是控制 "谁能访问哪个页面"。

7.1 路由守卫的分类与执行顺序

Vue Router 提供 3 类守卫,执行顺序如下(从跳转开始到结束):

  1. 全局前置守卫(beforeEach):所有路由跳转前执行(最常用,如登录验证);
  2. 路由独享守卫(beforeEnter):仅当前路由跳转前执行(单独配置某路由);
  3. 组件内守卫(beforeRouteEnter):进入组件前执行(组件内配置);
  4. 全局解析守卫(beforeResolve):所有路由跳转前,组件内守卫执行后;
  5. 全局后置钩子(afterEach):所有路由跳转后执行(无导航控制能力,如修改页面标题)。

7.2 最常用:全局前置守卫(beforeEach)

全局前置守卫是权限控制的核心,在src/router/index.ts中配置,每次路由跳转前都会触发,可控制 "是否允许跳转"。

实战:登录验证(未登录跳登录页)
TypeScript 复制代码
// src/router/index.ts
import { createRouter, createWebHistory } from "vue-router";
import Home from "@/views/Home.vue";
import Login from "@/views/Login.vue";

const routes = [
  { path: "/", redirect: "/home" },
  { path: "/home", component: Home },
  { path: "/about", component: () => import("@/views/About.vue") }, // 懒加载
  { path: "/login", name: "Login", component: Login },
  // 需要登录才能访问的页面:添加meta.requiresAuth标记
  {
    path: "/user/center",
    component: () => import("@/views/UserCenter.vue"),
    meta: { requiresAuth: true } // 标记:需登录才能访问
  },
  { path: "/:pathMatch(.*)*", component: () => import("@/views/NotFound.vue") }
];

const router = createRouter({
  history: createWebHistory(),
  routes
});

// 全局前置守卫:登录验证
router.beforeEach(async (to, from, next) => {
  // to:即将进入的目标路由(要去哪里)
  // from:当前离开的路由(从哪里来)
  // next:控制导航的函数(允许/禁止/重定向)

  // 1. 检查目标路由是否需要登录
  const needLogin = to.meta.requiresAuth;
  
  if (needLogin) {
    // 2. 检查本地是否有登录令牌(token)
    const token = localStorage.getItem("token");
    
    if (!token) {
      // 3. 未登录:重定向到登录页,并携带"当前路径"(登录后返回)
      next({
        name: "Login",
        query: { redirect: to.fullPath } // redirect:记录要访问的页面
      });
      return;
    }

    // 4. 已登录:验证token有效性(模拟接口)
    const isTokenValid = await mockValidateToken(token);
    if (!isTokenValid) {
      // token无效:清除token,重定向到登录页
      localStorage.removeItem("token");
      next({ name: "Login", query: { redirect: to.fullPath } });
      return;
    }
  }

  // 5. 无需登录或已登录:允许跳转
  next();
});

// 模拟token验证接口
const mockValidateToken = (token: string) => {
  return new Promise((resolve) => {
    // 实际项目中调用后端接口验证token
    setTimeout(() => {
      resolve(token === "valid-token"); // 假设valid-token是有效token
    }, 300);
  });
};

export default router;

总结

Vue Router 是 Vue SPA 应用的 "骨架",掌握它的核心配置(嵌套、传参、守卫),就能应对从简单博客到复杂管理系统的路由需求。建议从 "个人博客路由系统" 开始实践,逐步加入权限控制和面包屑导航,巩固所学知识点。

相关推荐
云和数据.ChenGuang4 小时前
vue钩子函数调用问题
前端·javascript·vue.js
鹏多多4 小时前
React动画方案对比:CSS动画和Framer Motion和React Spring
前端·javascript·react.js
亿元程序员4 小时前
8年游戏主程,一篇文章,多少收益?
前端
IT_陈寒4 小时前
5个Java 21新特性实战技巧,让你的代码性能飙升200%!
前端·人工智能·后端
咖啡の猫4 小时前
Vue内置指令与自定义指令
前端·javascript·vue.js
昔人'4 小时前
使用css `focus-visible` 改善用户体验
前端·css·ux
前端双越老师4 小时前
译: 构建高效 AI Agent 智能体
前端·node.js·agent
艾小码4 小时前
告别数据混乱!掌握JSON与内置对象,让你的JS代码更专业
前端·javascript