搞懂 SPA 再学路由!Vue Router 从0到完善 + 嵌套路由
说实话,回想刚学 Vue 路由的时候,整个人都是懵的。 为啥 Vue 非要整个 vue-router ? 以前写静态页面用 a 标签点一下就跳转了,啥配置都不用,为啥到了 Vue 项目就必须学路由?
相信很多小伙伴和我一样:路由配置会抄、代码会写,但本质一直没吃透,尤其是嵌套路由,一直搞不懂为啥要多套一层 children 。
所以这篇文章我按照自己从零理解、慢慢顿悟的顺序来分享,不讲死板概念、不搞老师讲课那套,纯纯是自己踩坑后的总结。
从「为什么要有路由」开始,到手把手迭代一版能直接上项目的完整路由,最后用最通俗的大白话讲明白「嵌套路由到底是个啥、到底有啥用」,看完你绝对彻底通透。
一、先搞懂根源:从网页的两种形态,弄懂「路由为什么必须学」
想要学明白路由,咱先别碰代码,先搞懂一个最核心的问题:前端路由到底是为了解决什么问题诞生的?
1. 老式网页 MPA:根本不需要路由
我们早年的普通网页、静态页、PHP 网页,都属于 MPA 多页面应用。
这种网页的逻辑特别简单粗暴:
- 每一个页面,都是一个独立的 html 文件
- 点一下导航,浏览器整页刷新、白屏、重新加载
跳转靠原生标签,浏览器自带能力,不用我们写任何逻辑说白了,这种模式就是:跳转 = 换一个全新网页。
这种模式下,完全不需要前端路由,因为浏览器已经帮我们把跳转干完了。
2. Vue 项目 SPA:逼出了「前端路由」
但我们现在写的 Vue 项目,全部都是 SPA 单页面应用。
最关键的核心:
Vue 项目从头到尾,只有唯一的一个 index.html。
你在项目里看到的很多个页面,根本不是一个个 html 文件,全部都是组件。
浏览器本来只会两种操作:
1. 打开新网页(整页刷新) 2. 原地不动
但 Vue 想要的效果是:地址栏变了、页面内容换了,但是网页不刷新、不白屏、不重载。
浏览器原生做不到这件事,所以就必须我们自己手动写一套规则——这套规则,就是vue-router。
到这里就彻底明白了: 不是我们想学路由,是 SPA 单页面模式,强制我们必须用路由!没有路由,Vue 项目根本跑不起来页面跳转。
3. 捋清楚 Vue 页面的真实层级以及SPA原理(再也不乱了)
一开始迷糊,就是因为搞不清谁套谁、谁先执行。我用最通俗的顺序给大家捋一遍:
浏览器最外层:index.html(纯空壳,只有一个 #app 容器)
↓
main.js(项目真正入口,最先执行)
↓
挂载路由 + 挂载根组件 App.vue
↓
App.vue(项目内部大壳子,唯一根组件)
↓
<router-view>(路由占位坑位)
↓
动态切换:Login.vue / Home.vue / 404.vue之类页面组件
总结大白话:
1. index.html 只是浏览器空壳,不执行任何逻辑
2. main.js 是真正的程序启动入口
3. App.vue 是 Vue 项目的内部根壳
4. 所有路由跳转,都在 App.vue 内部切换,不会跳出外层 index.html
这就是 SPA 无刷新跳转 的底层原理。
二、从零手写并完善一版路由
原理吃透,直接上手实战! 从最简骨架开始,逐版本叠加企业级功能,每一步都是项目刚需,最终产出一套「开箱即用、无 bug、带权限」的标准路由配置。
前置准备:
安装依赖:Vue3 专属路由版本为 vue-router@4 ,直接终端执行安装:npm install vue-router@4
规范项目文件结构:坚决不要把路由逻辑写在 main.js 里!项目越大越乱! 统一拆分独立文件更规范:
src/router/index.js // 所有路由配置统一管理
版本1:最简路由骨架(吃透路径匹配核心)
只保留最核心的「路径-组件」匹配逻辑,看懂这个就懂路由底层:
// src/router/index.js
import { createRouter, createWebHistory } from "vue-router";
// 静态引入页面组件(基础写法)
import Login from "@/views/Login.vue";
import Home from "@/views/Home.vue";
// 路由规则数组:路径和页面一一绑定
const routes = [
// 根路径重定向:打开根地址自动跳转到首页
{ path: "/", redirect: "/home" },
// 登录页路由
{ path: "/login", component: Login },
// 首页路由
{ path: "/home", component: Home },
];
// 创建路由实例
const router = createRouter({
// history 模式:去除地址栏 # 号,企业项目首选
history: createWebHistory(),
// 绑定路由规则
routes,
});//这两个参数必传
// 导出路由,供 main.js 全局挂载
export default router;
在创建路由时,我们还需要指定路由模式。vue-router 一共提供了三种:
- createWebHistory :history 模式,网址干净清爽,这是目前最常用的模式,正式项目首选,但需要后端配合处理刷新 404。
- createWebHashHistory :hash 模式,链接带 #,不用后端配置,刷新不会报错,适合内部工具。
- createMemoryHistory :内存模式,不依赖地址栏,多用于小程序、SSR 等非浏览器环境,前端项目很少用。
版本2:新增 meta 元信息(权限、标题必备)
真实开发中,我们需要给页面打标签:是否需要登录、页面标题、权限标识等。 meta 就是路由的自定义元信息,是做权限控制、页面标题的核心:
const routes = [
{ path: "/", redirect: "/home" },
{
path: "/login",
component: Login,
// 自定义页面属性
meta: {
requiresAuth: false, // false:不需要登录即可访问
title: "登录页面"
}
},
{
path: "/home",
component: Home,
meta: {
requiresAuth: true, // true:需要登录权限
title: "系统首页"
}
},
];
版本3:路由懒加载 + 404 兜底(性能+体验优化)
静态引入是纯新手写法! 项目页面多了之后,所有组件一次性加载,会导致首屏加载巨慢、卡顿。 统一用路由懒加载:访问哪个页面,就加载哪个页面的代码,大幅优化首屏性能。 同时新增 404 兜底页面,防止用户输错网址出现空白页:
import { createRouter, createWebHistory } from "vue-router";
// 懒加载写法:按需引入组件
const Login = () => import("@/views/Login.vue");
const Home = () => import("@/views/Home.vue");
const NotFound = () => import("@/views/NotFound.vue");
const routes = [
{ path: "/", redirect: "/home" },
{ path: "/login", component: Login, meta: { requiresAuth: false, title: "登录页面" } },
{ path: "/home", component: Home, meta: { requiresAuth: true, title: "系统首页" } },
// 全局404兜底:所有不存在的路径,全部匹配404页面
{ path: "/:pathMatch(.*)*", component: NotFound, meta: { title: "页面走丢了" } },
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
版本4:细节 bug 修复(上线必备优化)
两个项目高频 bug,提前根治:
1. 页面跳转后,滚动条不自动置顶,体验极差
2. 重复点击同一路由,控制台报红报错
优化后完整代码:
import { createRouter, createWebHistory } from "vue-router";
const Login = () => import("@/views/Login.vue");
const Home = () => import("@/views/Home.vue");
const NotFound = () => import("@/views/NotFound.vue");
const routes = [
{ path: "/", redirect: "/home" },
{ path: "/login", component: Login, meta: { requiresAuth: false, title: "登录页面" } },
{ path: "/home", component: Home, meta: { requiresAuth: true, title: "系统首页" } },
{ path: "/:pathMatch(.*)*", component: NotFound, meta: { title: "页面走丢了" } },
];
const router = createRouter({
history: createWebHistory(),
routes,
// 页面跳转自动回到顶部
scrollBehavior() {
return { top: 0, left: 0 };
},
});
// 修复:重复点击同一路由控制台报红问题
const originalPush = router.push;
router.push = function push(location) {
return originalPush.call(this, location).catch(() => {});
};
export default router;
版本5:最终完整版(路由守卫+权限控制)
这是可以直接上线的终极版本! 新增全局路由前置守卫,实现三大核心功能:
- 自动修改浏览器页面标题
- 未登录禁止访问首页等权限页面
- 已登录禁止重复进入登录页
完整版本:
// src/router/index.js
import { createRouter, createWebHistory } from "vue-router";
// 路由懒加载引入所有页面
const Login = () => import("@/views/Login.vue");
const Home = () => import("@/views/Home.vue");
const NotFound = () => import("@/views/NotFound.vue");
// 路由规则配置
const routes = [
{ path: "/", redirect: "/home" },
{ path: "/login", component: Login, meta: { requiresAuth: false, title: "登录页面" } },
{ path: "/home", component: Home, meta: { requiresAuth: true, title: "系统首页" } },
{ path: "/:pathMatch(.*)*", component: NotFound, meta: { title: "页面走丢了" } },
];
// 创建路由实例
const router = createRouter({
history: createWebHistory(),
routes,
// 跳转页面自动置顶
scrollBehavior() {
return { top: 0, left: 0 };
},
});
// 修复重复导航报错
const originalPush = router.push;
router.push = function push(location) {
return originalPush.call(this, location).catch(() => {});
};
// 全局前置路由守卫:页面跳转前触发
router.beforeEach((to, from) => {
// 1. 自动设置页面标题
document.title = to.meta.title || "后台管理系统";
// 2. 获取本地登录令牌,判断登录状态
const token = localStorage.getItem("token");
// 需登录权限的页面:无token强制跳转登录页
if (to.meta.requiresAuth) {
if (!token) return "/login";
} else {
// 已登录状态,禁止再次进入登录页
if (token && to.path === "/login") return "/home";
}
// 放行,允许页面跳转
return true;
});
export default router;
最后一步:全局挂载路由
路由文件写完后,必须在 main.js 挂载才能全局生效,标准写法:
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
// 引入路由文件
import router from './router'
// use(router) 全局注册路由
createApp(App).use(router).mount('#app')
补充:路由最常用的基本使用方法
路由配置好之后,在页面里最常用的就两件事:跳转页面 和 拿当前路由信息,日常开发基本就靠这几个。
1. 模板里跳转:router-link
相当于不刷新页面的 a 标签,直接用:
<router-link to="/home">首页</router-link>
<router-link to="/user">用户管理</router-link>
2. JS 逻辑里跳转(点击按钮常用)
//在 <script setup> 里通过 useRouter 实现跳转,最真实常用的写法:
<template>
<button @click="goHome">跳转到首页</button>
</template>
<script setup>
import { useRouter } from 'vue-router'
// 获取路由实例
const router = useRouter()
// 点击跳转
const goHome = () => {
router.push('/home')
}
</script>
3. 返回上一页
const goBack = () => {
router.back()
}
4. 获取当前路由信息(路径、参数等)
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
// 当前路径
console.log(route.path)
// 获取 ?id=123 这类参数
console.log(route.query.id)
</script>
5. 路由显示出口:router-view
所有路由页面都渲染在这个位置,必须写在 App.vue 或布局组件里:
<router-view />
三、嵌套路由梳理
前面写的都是一级普通路由,逻辑很简单:跳转即整个内部页面全部替换。 但我们真实开发的后台系统,几乎全部依赖嵌套路由!
1. 通俗理解嵌套路由
先看后台系统的通用布局:
- 左侧侧边栏、顶部导航栏:永久固定,不会变化
- 只有中间内容区域:点击菜单实时切换
这就是嵌套路由的专属场景!
大白话直接总结两者区别:
- 普通一级路由:替换整个大页面(壳子+内容全换)
- 嵌套路由: 外层大壳子固定不动,只替换壳子内部的小块内容
嵌套路由核心本质: 在一个父页面(布局壳子)里,再写一个 ( <router-view > )坑位,专门用来渲染子页面,实现局部无刷新切换。
2. 为什么必须用嵌套路由?
我全部写一级路由不行吗?
行是行,但会出现三个致命问题,项目完全无法使用:
1. 代码极度冗余:每个页面都要重复写侧边栏、顶部导航,重复代码满天飞
2. 页面体验极差:切换页面会刷新整个布局,出现闪屏、白屏
3. 状态全部重置:菜单折叠、用户信息、滚动位置等状态,切换页面全部清零
只要是「固定布局 + 动态内容」的页面,嵌套路由是刚需。
3. 嵌套路由实战写法
第一步:新建全局布局外壳组件 Layout.vue
这是所有子页面的父壳子,侧边栏、头部全部写在这里,永久固定: 顶部导航 / 侧边信息
<!-- Layout.vue 固定结构 -->
<template>
<div class="layout">
<!-- 侧边栏 + 顶部导航 -->
<div class="sidebar">...</div>
<div class="header">...</div>
<!-- 子页面渲染坑位,必须写! -->
<router-view />
</div>
</template>
第二步:配置 children 嵌套路由规则
父路由绑定布局壳子, children 数组存放所有子页面,结构超级清晰:
// 引入布局组件 & 子页面组件
const Layout = () => import("@/views/Layout/Layout.vue");
const Login = () => import("@/views/Login.vue");
const User = () => import("@/views/User.vue"); // 用户管理子页面
const Order = () => import("@/views/Order.vue"); // 订单管理子页面
const NotFound = () => import("@/views/NotFound.vue");
const routes = [
{ path: "/", redirect: "/login" },
{ path: "/login", component: Login, meta: { requiresAuth: false, title: "登录页面" } },
{
// 父路由地址
path: "/home",
// 父路由绑定全局布局壳子
component: Layout,
meta: { requiresAuth: true, title: "系统首页" },
// 嵌套子路由:所有内部切换的页面
children: [
// 子路由路径无需加 /
{ path: "user", component: User, meta: { title: "用户管理" } },
{ path: "order", component: Order, meta: { title: "订单管理" } },
],
},
{ path: "/:pathMatch(.*)*", component: NotFound, meta: { title: "页面走丢了" } },
];
小技巧:想让进入 /home 时默认显示某个子页面,可以加一行重定向:
redirect: '/home/user'
这样用户一进后台,就自动显示用户管理页面,不会空白。
嵌套路由 3 个必记关键点
1. 父路由组件(Layout)内部必须写 ( <router-view >),子页面才知道渲染在哪里。
2. 子路由 path 不要以 / 开头,直接写 user 、 order ,会自动拼接父路由路径。
3. 子组件和普通页面写法完全一致,都是 .vue 文件,只需在路由里正确 import 引入即可。
大白话总结:一级路由管整个大页面,嵌套路由管页面里的小模块!
四、全文核心思路复盘
最后串联全文逻辑,彻底打通 Vue-Router 知识体系:
1. 先懂底层逻辑:区分 MPA 和 SPA 差异,明白 Vue 单页面无刷新跳转的特性,决定了项目必须依赖 vue-router,搞懂技术存在的意义,不再死记硬背。
2. 渐进式手写企业路由:从最简路由骨架起步,一步步叠加 meta 元信息、懒加载性能优化、404 兜底、滚动优化、报错容错,最后搭配全局路由守卫,实现权限控制、标题自动修改,迭代出一套完整、可直接上线的标准化路由配置,每一个功能都对应真实项目需求。
3. 吃透嵌套路由核心:区分普通路由和嵌套路由的使用场景,掌握「父布局壳子 + 子路由嵌套」的实战通用写法,解决传统一级路由的代码冗余、页面闪屏、状态重置问题,适配所有后台管理系统布局。
本篇 Vue Router 完整知识点梳理到此结束,下一篇实战梳理 Vue3 全局状态管理核心:Pinia。