SPA架构

什么是 SPA 架构

我们知道 SPA 是单页面的前端架构,但是,单页面应用(SPA)不是简单的只有一个HTML文件的形式,而是一种应用架构模式:

  • SPA = Single Page Application(单页应用)
  • 整个应用只加载一次 HTML 文件,后续所有页面切换、数据交互都在这一个页面内完成,不重新加载整个页面

SPA 就像一个「容器」,首次加载时把所有必需的 JS、CSS 都加载完成(或按需加载),之后用户操作时,只动态更新页面内的「部分内容」,URL 变化但浏览器不刷新。

SPA 架构做的前端有这样的核心特征

  • 应用仅包含 1 个核心 HTML(比如 index.html),所有页面都是这个 HTML 内的「组件片段」;
  • 无刷新切换,页面切换靠 JS 动态渲染组件(销毁旧组件、挂载新组件),浏览器地址栏 URL 可变化,但不会触发页面刷新(区别于传统网站的「跳转 = 刷新」);
  • 数据驱动视图,页面内容更新依赖「数据变化」(比如 Vue 的响应式),而非重新加载 DOM;交互时只通过 AJAX/Fetch 向后端请求「数据」(JSON 格式),不请求完整 HTML。

SPA架构是如何工作的

SPA 架构是这样工作的

  • 首次加载:只加载「容器 + 核心资源」
    • 当用户输入 https://xxx.shop 访问应用时:
      1. 浏览器向服务器发送请求,只获取 1 个 HTML 文件(index.html);
      2. 这个 HTML 非常简洁,只有一个空的「根容器」(比如 <div id="app"></div>),以及引入的 Vue、Vue Router、应用 JS/CSS 等资源;
      3. 浏览器加载完资源后,Vue 实例挂载到根容器,执行初始化逻辑(比如路由匹配);
      4. 根据当前 URL(默认是 /),Vue Router 匹配到「首页组件」,动态渲染到根容器中 —— 首次页面展示完成。
  • 页面切换:只更新「组件 + URL」,不刷新页面
    • 当用户点击「详情页」按钮时(由 Vue Router 的 <router-link> 实现):
      1. 前端 JS 拦截点击事件(阻止浏览器默认的「跳转刷新」行为);
      2. 通过浏览器的 history API 或 hash 特性,修改地址栏 URL(比如从 / 变成 /detail/123);
      3. Vue Router 监听 URL 变化,匹配到「详情页组件」;
      4. 前端 JS 动态操作 DOM:销毁当前的「首页组件」DOM,把「详情页组件」的 DOM 插入到 <router-view> 中;
      5. (一般会有)详情页组件通过 AJAX 请求后端,获取 productId=123 的产品数据,渲染到页面 —— 整个过程无刷新。
  • 数据交互:只请求「数据」,不请求「HTML」
    • SPA 中所有数据交互(列表查询、提交表单、详情查询等),都是通过 AJAX/Fetch 向后端请求「JSON 格式的数据」,而非完整的 HTML 页面。
    • 后端只需要返回 JSON 数据(比如 { "id": 123, "name": "手机", "price": 3999 }),不需要渲染完整 HTML—— 这也是 SPA 后端常被称为「接口服务」的原因。

SPA的优缺点

SPA 能成为现代前端开发的主流架构,核心是解决了 MPA 的「交互体验差」问题,具体优势有:

  1. 极致的交互流畅度:无刷新切换页面,用户操作没有「等待刷新」的延迟感,体验接近原生 App(这是 SPA 最核心的价值);
  2. 减少服务器压力:后端只需要提供数据接口,不用渲染 HTML 页面,计算成本降低,相同服务器能支撑更多用户;
  3. 前后端彻底分离:前端专注于「视图渲染和交互」,后端专注于「数据处理和接口提供」,开发职责清晰,可并行开发(比如前端先 Mock 数据开发,后端同步写接口);
  4. 组件复用性高:SPA 基于组件化开发(比如 Vue 组件),页面片段(导航栏、列表项、按钮)可复用,降低代码冗余;
  5. 可维护性强:前端逻辑集中在一个应用中,路由、状态、组件的管理更规范(配合 Vuex/Pinia 等状态管理库),后期迭代更高效。

但是 SPA 不是银弹,也存在明显缺点,实际开发中需要针对性解决:

  1. 首次加载速度慢:首次需要加载所有核心 JS、CSS 和框架(比如 Vue + Vue Router + Vuex + 业务 JS),如果资源体积大,会导致「白屏时间长」;
    • 解决方案:代码分割(按需加载)、资源压缩、CDN 加速、首屏渲染优化(比如骨架屏);
  2. SEO 不友好:搜索引擎爬虫默认只爬取 HTML 内容,而 SPA 首次渲染的 HTML 是空的(内容由 JS 动态生成),导致爬虫无法抓取页面信息,影响搜索排名;
    • 解决方案:服务端渲染(SSR,比如 Nuxt.js)、预渲染(Prerender)、动态渲染(根据访问者是爬虫还是用户,返回不同内容);
  3. 前进 / 后退按钮问题:早期 SPA 用 hash 模式(URL 带 #),浏览器默认的前进 / 后退按钮不生效,后来通过 history API 解决了这个问题(Vue Router 已内置支持);
  4. 路由管理依赖前端:MPA 的路由由后端控制,而 SPA 必须靠前端路由(如 Vue Router)管理 URL 与组件的映射,需要额外学习和配置;(这算缺点吧)))
  5. 内存泄漏风险:SPA 长时间运行(比如原生 App 内嵌的 H5 页面),组件频繁创建和销毁如果处理不当,可能导致内存泄漏,影响性能;
    • 解决方案:合理销毁定时器、解绑事件监听、清理全局变量等。

前端路由在SPA架构的重要性

那么前端路由在 SPA 架构中就是最重要的了,可以说前端路由 使得 SPA 架构得以实现然后大规模流行

假设你做了一个 Vue 单页应用(SPA),里面有 3 个核心页面:

  • 首页(展示产品列表)
  • 详情页(展示单个产品信息)
  • 个人中心(展示用户信息)

如果没有路由管理,你可能会这么写代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!-- App.vue -->
<template>
<div>
<!-- 导航按钮 -->
<button @click="showHome">首页</button>
<button @click="showDetail">详情页</button>
<button @click="showUser">个人中心</button>

<!-- 页面容器:通过 v-if 控制显示哪个页面 -->
<Home v-if="currentPage === 'home'" />
<Detail v-if="currentPage === 'detail'" />
<User v-if="currentPage === 'user'" />
</div>
</template>

<script>
export default {
data() {
return { currentPage: 'home' } // 控制当前显示的页面
},
methods: {
showHome() { this.currentPage = 'home' },
showDetail() { this.currentPage = 'detail' },
showUser() { this.currentPage = 'user' }
}
}
</script>

这种写法能实现页面切换,但有 4 个致命问题:

  1. 无法通过 URL 直接访问页面:比如用户想分享「详情页」,复制链接发给别人,打开还是首页(因为 URL 没变);
  2. 没有浏览器历史记录:点击浏览器「后退 / 前进」按钮,不会切换到之前看过的页面;
  3. 难以传递参数:比如想访问「产品 ID=123 的详情页」,没法通过 URL 携带参数(只能用全局变量 / 状态管理,麻烦且不直观);
  4. 代码冗余:页面多了之后,v-if 判断会越来越复杂,导航逻辑和页面渲染混在一起,维护困难。

而路由管理的出现,就是解决单页应用的 URL 与页面的映射关系的问题,让 URL 能对应到具体的页面,同时支持浏览器历史、参数传递、导航控制等功能。

路由相关内容

什么是路由

路由(Router)的本质是「映射规则」:URL 路径 → 页面组件

路由在前端开发中扮演着至关重要的角色,它的核心本质是 URL 与 UI 组件之间的映射关系管理。从传统的多页面应用到现代单页面应用,路由的演进体现了Web开发理念的重大转变。

传统网站的路由是每次URL变化都向服务器请求新的页面,这样每次请求都会导致完整页面刷新,体验很不好。也就是说,传统路由是交给后端来做的,URL → 后端资源(返回 HTML / 数据),每次路由切换都要向后端发请求,这就是以前的多页应用架构 MPA,比如 JSP/PHP 网站

前端路由管理,就是在前端(浏览器端)维护这套映射规则,让用户在不刷新页面的情况下,通过改变 URL 来切换不同的页面组件(因为是单页应用 SPA,整个应用只有一个 HTML 文件,刷新会重新加载整个应用)。

前端路由有 3 个核心能力

  1. URL 与组件的绑定:比如 / → 首页组件,/detail/123 → ID=123 的详情组件;
  2. 无刷新页面切换:通过 JS 监听 URL 变化,动态渲染对应的组件(不触发浏览器刷新,体验流畅);
  3. 浏览器历史兼容:支持点击「后退 / 前进」按钮切换页面(本质是利用浏览器的 history API 或 hash 特性)。

举一个直观的例子:

你打开 Vue Router 的官方文档,打开入门部分(https://router.vuejs.org/zh/guide/),我们学习命名视图,点击发现,上面的 URL 变成了https://router.vuejs.org/zh/guide/essentials/named-views.html,可以发现是 URL 的变化带我们找到了正确的前端内容

image-20251202204818399

前端路由与后端的关系

前端路由本身不依赖后端,但页面数据、权限等可能需要和后端交互

新型的前端路由摆脱了每次路由都需要向后端发送请求,而是监听 URL 变化 → 渲染对应的组件,这个过程完全在浏览器端完成

举个例子:

  • 你在 Vue 应用中配置了 { path: '/detail', component: Detail }
  • 当用户点击「详情页」导航时,前端路由会做两件事:
    1. 改变浏览器 URL(比如从 / 变成 /detail);
    2. 销毁当前的首页组件,渲染 Detail 组件;
  • 整个过程没有发送任何 HTTP 请求到后端,完全是前端 JS 操作。

虽然路由切换是纯前端的,但也有很多场景需要和后端交互(不是路由本身要交互,而是路由对应的页面 / 功能需要):

  • 页面数据获取:进入详情页 /detail/123 后,需要向后端请求 productId=123 的产品数据;
  • 路由权限控制:某些路由(比如 /admin 管理员页面)需要先向后端验证用户是否有管理员权限,没有的话跳转到登录页;

这是很常见的需求,所以说前端路由完全抛开后端内容是不现实的

Vue Router 在 Vue 中扮演者什么角色

Vue Router 是 Vue 官方提供的路由管理库,专门为 Vue 应用设计,和 Vue 无缝集成。它的核心价值就是帮我们解决前面提到的无前端路由管理的痛点

  1. 简洁的路由配置:用少量代码就能实现 URL 与组件的映射(不用写一堆 v-if);
  2. 内置导航功能:提供 <router-link> 组件(替代普通按钮),点击自动切换 URL 和组件,还支持高亮当前路由;
  3. 参数传递:支持通过 URL 传递参数(比如 /detail/:id),组件内可直接获取;
  4. 嵌套路由:支持路由嵌套(比如 /user/profile/user/orders),对应组件的嵌套渲染;
  5. 路由守卫:可以在路由切换前后做拦截(比如权限验证、数据预加载);
  6. 历史模式支持:支持两种 URL 模式(hash 模式:#/detailhistory 模式:/detail),满足不同需求。

Vue Router如何安装配置

一句话,都前端了,直接 npm 把包导进来不就好了

Vue 3 对应的路由包是 vue-router@4

使用 Vue CLI

如果你的项目是通过 vue create xxx 创建的(Vue CLI 脚手架)

1
2
3
4
5
6
7
8
# npm(推荐,最常用)
npm install vue-router@4 --save

# 或 yarn(需先安装 yarn)
yarn add vue-router@4

# 或 pnpm(更快的包管理器)
pnpm add vue-router@4
  • --save:可选(npm 5+ 已默认添加到 package.json 依赖中),表示将包写入生产依赖;
  • 安装成功后,package.json 中会新增依赖:"vue-router": "^4.x.x"

使用 Vite

安装和配置逻辑和 Vue CLI 基本一致

1
2
3
4
5
6
7
8
# npm
npm install vue-router@4 --save

# 或 yarn
yarn add vue-router@4

# 或 pnpm
pnpm add vue-router@4

直接下载 / CDN

https://unpkg.com/vue-router@4

Unpkg.com 提供了基于 npm 的 CDN 链接。上述链接将始终指向 npm 上的最新版本。 你也可以通过像 https://unpkg.com/vue-router@4.0.15/dist/vue-router.global.js 这样的 URL 来使用特定的版本或 Tag。

这将把 Vue Router 暴露在一个全局的 VueRouter 对象上,例如 VueRouter.createRouter(...)

一套路由配置的完整架构

在下好了包之后,来看一下完整的 Vue 路由结构

  1. 安装 Vue Router

  2. 创建路由配置文件

    路由器实例是通过调用 createRouter() 函数创建的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    // src/router/index.js

    // 1. 从 vue-router 中导入核心函数
    import { createRouter, createWebHistory } from 'vue-router'

    // 2. 导入需要映射的页面组件(可自己创建,这里举示例)
    // 建议:页面组件放在 src/views 文件夹下(和公共组件 src/components 区分)
    import Home from '../views/Home.vue' // 首页组件
    import Detail from '../views/Detail.vue' // 详情页组件
    import NotFound from '../views/NotFound.vue' // 404 组件

    // 3. 配置路由规则:URL 路径 → 对应组件
    const routes = [
    {
    path: '/', // 访问路径(根路径)
    name: 'Home', // 路由名称(可选,用于编程式导航)
    component: Home // 对应的组件
    },
    {
    path: '/detail/:id', // 动态路由(带参数 id,比如 /detail/123)
    name: 'Detail',
    component: Detail
    },
    {
    path: '/:pathMatch(.*)*', // 404 路由(匹配所有未定义的路径)
    name: 'NotFound',
    component: NotFound
    }
    ]

    // 4. 创建路由实例
    const router = createRouter({
    // Vite 中 import.meta.env.BASE_URL 会读取 vite.config.js 中的 base 配置(默认是 '/')
    history: createWebHistory(import.meta.env.BASE_URL),
    routes
    })

    // 5. 导出路由实例(供 main.js 导入)
    export default router
    • 这里的 routes 选项定义了一组路由,把 URL 路径映射到组件。其中,由 component 参数指定的组件就是被 <RouterView> 渲染的组件。这些路由组件通常被称为视图,但本质上它们只是普通的 Vue 组件。
  3. 在 Vue 根实例中注入路由

    1
    2
    3
    4
    5
    6
    7
    8
    // src/main.js
    import { createApp } from 'vue'
    import App from './App.vue'
    import router from './router'

    createApp(App)
    .use(router) // 注入路由
    .mount('#app')
  4. 创建组件 + 配置 App.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // src/views/Home.vue

    <template>
    <div>
    <h1>首页</h1>
    <!-- 路由链接:替代 a 标签,无刷新跳转 -->
    <router-link to="/detail/123">查看产品 123 详情</router-link>
    </div>
    </template>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // src/views/Detail.vue

    <template>
    <div>
    <h1>详情页</h1>
    <p>当前产品 ID:{{ $route.params.id }}</p> <!-- 获取路由参数 -->
    <router-link to="/">返回首页</router-link>
    </div>
    </template>
    1
    2
    3
    4
    5
    6
    // src/views/NotFound.vue

    <template>
    <h1>404 - 页面不存在</h1>
    <router-link to="/">返回首页</router-link>
    </template>
  5. 在 App.vue 中使用路由视图

    修改 src/App.vue,用 <router-view> 作为组件渲染的占位符(路由匹配到的组件会在这里显示):

    1
    2
    3
    4
    5
    6
    7
    <!-- src/App.vue -->
    <template>
    <div id="app">
    <!-- 路由视图:核心占位符 -->
    <router-view />
    </div>
    </template>
  6. 运行测试

也就是说,一套 Vue Router 完整的前端路由是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
src/
├── router/ # 路由核心目录(不再是单个 index.js)
│ ├── index.js # 路由入口(创建路由实例、注入守卫、导出)
│ ├── routes.js # 路由规则配置(按模块拆分,清晰易维护)
│ ├── guards.js # 路由守卫(全局守卫、权限控制逻辑)
│ └── utils.js # 路由工具函数(如路由跳转封装、参数处理)
├── views/ # 页面级组件(路由对应的页面)
│ ├── Home/ # 首页(推荐文件夹形式,内部可放子组件)
│ │ └── index.vue
│ ├── Detail/ # 详情页
│ │ └── index.vue
│ ├── User/ # 用户模块(嵌套路由示例)
│ │ ├── Profile.vue # 个人资料
│ │ └── Orders.vue # 我的订单
│ ├── Admin/ # 管理员模块(权限控制示例)
│ │ └── index.vue
│ └── NotFound/ # 404 页面
│ └── index.vue
├── components/ # 公共组件(非路由组件,如导航栏、页脚)
│ ├── Navbar.vue # 全局导航栏
│ └── Footer.vue # 全局页脚
├── App.vue # 根组件(包含全局布局、路由视图)
└── main.js # 入口文件(注入路由)

我只是展示了其中最关键的一部分

Vue Router 基础

创建路由模块文件

首先,在使用 Vue Router 进行路由管理的时候,我们需要进行路由配置文件的定义

一般情况下,我们会在 项目 src 目录下新建 router/index.js,集中管理路由配置和实例创建。

image-20251204165519017

那么,路由模块文件如何编写呢?

首先,一个准的 Vue 路由模块通常包含以下部分:

  1. 依赖导入(Vue、VueRouter)
  2. 页面组件导入
  3. 路由规则配置(routes 数组)
  4. 路由实例创建(new VueRouter()
  5. 路由守卫配置(全局导航守卫)
  6. 导出路由实例

首先导入依赖

1
import VueRouter from 'vue-router'

然后,依赖导入了就要被使用,我们需要为路由模块文件安装 VueRouter 插件,这是 Vue2 的写法,Vue3 下面说

1
Vue.use(VueRouter)
  • Vue.use() 是 Vue 提供的一个插件安装方法,用于在 Vue 全局范围内注册插件,使其功能可以在整个应用中使用。它接受一个插件对象(或函数)作为参数,并会自动调用插件内部的 install 方法(如果是对象的话)。
  • Vue.use(VueRouter)具体做了如下内容
    • 全局注册路由相关组件
    • 注入路由实例到 Vue 原型:在 Vue 原型上添加 $router(路由实例)和 $route(当前路由信息)属性,让所有组件都能通过 this.$routerthis.$route 访问路由功能,这个其实算是 Vue2 的写法
    • 初始化路由相关逻辑

而路由配置的核心是 routes 数组,每个路由对象的关键属性如下

image-20251204170159470
1
2
3
4
5
6
{
path: '/project-detail/:id', // 路由路径,`:id` 表示动态参数
name: 'ProjectDetail', // 路由名称(唯一,用于编程式导航)
component: ProjectDetail, // 对应页面组件
children: [] // 子路由(嵌套路由)
}
  • 其中,component参数指定的这些路由组件通常被称为视图,但本质上它们只是普通的 Vue 组件。
  • 注意,路由规则的 path 要遵从唯一性,避免定义重复的 path(如两个 /about),否则后定义的会覆盖前一个。
  • 注意,不要直接修改 routes 数组,因为数组是只读的。

路由可以当然可以嵌套

image-20251204170231634

当路由足够大的时候,就需要涉及到路由拆分了,以我的项目为例子(我们的前端不习惯拆路由)

1
2
3
4
5
6
7
8
src/
├── router/
│ ├── index.js # 路由入口(创建实例、配置守卫)
│ ├── routes/ # 拆分的路由配置文件
│ │ ├── auth.js # 认证相关路由(登录、注册等)
│ │ ├── project.js # 项目相关路由
│ │ ├── knowledge.js # 知识库相关路由
│ │ └── index.js # 合并所有路由配置

在路由模块组件中的 index.js 进行路由配置的合并

1
2
3
4
5
6
7
8
9
10
11
import authRoutes from './auth'
import projectRoutes from './project'
import knowledgeRoutes from './knowledge'

// 根路由(如重定向)
const rootRoutes = [
{ path: '/', redirect: '/home' }
]

// 合并所有路由
export default [...rootRoutes, ...authRoutes, ...projectRoutes, ...knowledgeRoutes]

生产环境下,我们为了避免一次初始化的内容太多,通常会使用路由懒加载,它能让路由对应的组件在第一次被访问时才加载,而不是在应用初始化时就全部加载。

Vue 中通过动态import()语法实现路由懒加载,webpack 等构建工具会自动将其打包为单独的代码块。

将直接导入的组件替换为动态导入函数,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const routes = [
{
path: '/login',
name: 'Login',
// 懒加载Login组件
component: () => import('../views/Login.vue')
},
{
path: '/home',
name: 'Home',
// 懒加载Home组件
component: () => import('../views/Home.vue')
},
{
path: '/knowledge-base',
name: 'KnowledgeBase',
component: () => import('../views/KnowledgeBase.vue'), // 父组件懒加载
children: [
{
path: 'catalog',
component: () => import('../views/KnowledgeBaseCatalog.vue') // 子组件懒加载
},
// ...其他子路由
]
},
// ...其他路由
]

创建路由器实例

路由器实例是通过调用 createRouter() 函数创建的,传入核心配置项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 创建路由器实例
const router = createRouter({
// 路由模式:history 模式(无 #)或 hash 模式(带 #)
history: createWebHistory(), // HTML5 History 模式(推荐,需后端配置)
// history: createWebHashHistory(), // Hash 模式(无需后端配置)
// 路由规则数组
routes,
//其他可选配置项(如 scrollBehavior 等)
scrollBehavior(to, from, savedPosition) {
// 自定义路由切换时的滚动行为
return savedPosition || { top: 0 } // 回到顶部
}
})

// 导出实例,供 Vue 应用挂载
export default router

其中,routers 就是传入前面定义的路由规则数组(const routes = [...]),即把所有页面的路由配置注入到路由器实例中。

路由的模式常见有两种,在下面细说,先知道history 选项控制了路由和 URL 路径是如何双向映射的就可以

  • 'history':使用 HTML5 History API(依赖浏览器支持),URL 中不包含 #(如 https://example.com/home),更美观但需要后端配置支持(避免刷新 404)。
  • 'hash'(默认值):使用 URL 中的哈希值(# 后面的部分,如 https://example.com/#/home),兼容性更好,但 URL 中会带 #

还有一种写法来创建路由器实例

通过 new VueRouter(...) 创建的,这是 Vue Router 3.x 版本的语法(适用于 Vue 2)。而 createRouter 是 Vue Router 4.x 版本(适用于 Vue 3)的语法,两者属于不同版本的 API,核心作用一致但写法不同。

1
2
3
4
5
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
  • 单独说一下这个配置项:base: process.env.BASE_URL
    • 定义应用的基础路径,所有路由路径都会基于这个值拼接。
    • process.env.BASE_URL 是 Vue CLI 项目中默认的环境变量,默认值为 '/',可在 vue.config.js 中通过 publicPath 配置修改

最后,路由器组件中还会有这样一段代码

1
export default router

这行代码的作用是 将创建好的路由器实例导出,供整个项目使用

导出的 router 是一个单例对象(整个项目中只有一个路由器实例),所有组件通过 this.$router 访问的都是同一个实例,确保路由状态(如当前路径、历史记录)在全局一致。

注册路由器插件

一旦创建了我们的路由器实例,我们就需要将其注册为插件,这一步骤可以通过调用 use() 来完成。

在 Vue 中,app.use(router)(Vue 3)或 Vue.use(VueRouter)配合根实例注入(Vue 2) 的核心作用是将路由器实例与 Vue 应用关联,让路由功能在整个应用中生效。这一步是路由系统融入 Vue 生态的关键,类似于 “激活” 路由功能。

1
2
// Vue2
createApp(App).use(router).mount('#app')

或等价地:

1
2
3
4
// Vue3
const app = createApp(App)
app.use(router)
app.mount('#app')
  • 一个 Vue 应用只能挂载一个路由器实例,避免多次 app.use(router)

app.use(router) 具体做了什么?

  1. 全局注入路由实例
    • 向 Vue 应用注入路由器实例(router),使得所有组件都能通过 this.$router 访问路由实例(如调用 this.$router.push() 实现编程式导航),通过 this.$route 访问当前路由信息(如路径、参数等)。
    • 例如你的代码中,组件内可以通过 this.$router.push('/home') 跳转到首页,这依赖于路由实例被全局注入。
  2. 注册全局路由组件
    • 自动注册<router-view><router-link>两个核心组件,无需在每个组件中单独导入即可使用:
      • <router-view>:用于渲染当前路由匹配的组件(例如访问 /login 时,它会渲染 Login 组件)。
      • <router-link>:用于生成路由链接(替代 <a> 标签,避免页面刷新)。
  3. 绑定路由与应用生命周期
    • 将路由系统与 Vue 应用的生命周期钩子关联,确保当路由状态变化(如 URL 改变)时,Vue 能自动触发组件更新,实现 “URL 变化 → 视图更新” 的响应式联动。
    • 例如用户从 /login 跳转到 /home 时,<router-view> 会自动卸载 Login 组件并挂载 Home 组件。
  4. 初始化路由监听
    • 启动对浏览器 URL 变化的监听(如 popstate 事件),并将 URL 变化同步到路由实例的状态中,反之,当通过 $router.push 改变路由时,也会同步更新浏览器 URL。

在, Vue 项目中,Vue.use(VueRouter)(Vue 2)或 app.use(router)(Vue 3)通常是在入口文件中执行的(一般是 main.jsmain.ts

在 Vue 2 项目中,对应逻辑是在 main.js 中先导入 router 并注入根实例,再调用 $mount()

image-20251204174117480
1
2
3
4
5
6
7
8
9
// main.js(Vue 2 示例)
import Vue from 'vue'
import App from './App.vue'
import router from './router'

new Vue({
router, // 注入路由实例(相当于 Vue 3 的 app.use(router))
render: h => h(App)
}).$mount('#app') // 必须在注入后执行挂载

而在 index.js 中,我们通过 Vue.use(VueRouter) 完成了Vue Router的全局注册

这个例子是 Vue2 的写法,完整的 Vue3 的相关写法如下,此时,就通过应用实例的 app.use(router) 注册Vue Router

1
2
3
4
5
6
7
8
9
// main.js(Vue3入口文件)
import { createApp } from 'vue'
import App from './App.vue'
import router from './router' // 导入上面创建的路由实例

// 创建应用实例并使用路由插件
const app = createApp(App)
app.use(router) // Vue3中通过应用实例的use方法注册插件
app.mount('#app')

很多人以为是在 App.vue 中,实际上并不是,原因是:

  • 入口文件是初始化 Vue 实例(或应用)的地方,需要在这里完成全局插件的注册
  • 路由插件需要在应用启动时就完成注册,才能保证整个应用的路由功能正常工作

访问路由器和当前路由

我们经常需要在应用的其他地方访问路由器。

  • 路由器(Router):全局路由实例,用于控制路由跳转、配置路由规则等。
  • 当前路由(Route):表示当前活跃的路由信息,包含路径、参数、查询参数等。

如果你是从 ES 模块导出路由器实例的,你可以将路由器实例直接导入到你需要它的地方。在一些情况下这是最好的方法,但如果我们在组件内部,那么我们还有其他选择。

路由访问方式的 Vue2 和 Vue3 写法差异如下

  • Vue2:组件中通常通过 this.$router 访问路由器实例,通过 this.$route 访问当前路由信息

  • Vue3:

    • 选项式 API 仍可使用 this.$router/this.$route

    • 组合式 API 中使用useRouter()useRoute()访问路由器实例和当前路由。

      1
      2
      3
      4
      import { useRouter, useRoute } from 'vue-router'

      const router = useRouter() // 替代 this.$router
      const route = useRoute() // 替代 this.$route

访问路由器基本就是用于跳转,历史操作记录等,我们下面的例子都用 Vue3 的形式写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { useRouter } from 'vue-router';

export default {
setup() {
const router = useRouter();

// 跳转路由
const goToProjectDetail = (projectId) => {
router.push(`/project-detail/${projectId}`);
};

// 带查询参数的跳转
const goToKnowledgeBase = () => {
router.push({ path: '/knowledge-base', query: { id: 1 } });
};

return { goToProjectDetail, goToKnowledgeBase };
}
};
  • 注意,Vue3 中 useRoute() 返回的 route 对象是响应式的,需通过 watch 监听变化。

访问当前路由(Route)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { useRoute } from 'vue-router';

export default {
setup() {
const route = useRoute();

// 获取当前路径
const currentPath = route.path;

// 获取动态路由参数
const projectId = route.params.id;

// 获取查询参数
const query = route.query;

// 监听路由变化(响应式)
watch(
() => route.params,
(newParams) => {
console.log('路由参数变化:', newParams);
}
);

return { currentPath, projectId };
}
};

为项目添加路由入口

现在我们所有准备工作都完成了,但是其实还是不能直接显示各路由地址对应的组件,因为我们还缺少关键的一步——在项目的页面中,写入一个路由入口。

先了解<RouterLink>标签,这个标签是干什么的

1
2
<RouterLink to="/">Home</RouterLink>
<!-- 渲染后会生成类似 <a href="/">Home</a> 的元素,但点击时不会刷新页面 -->
  • 类似于 HTML 中的 <a> 标签
  • 其中的一些核心属性如下
    • to:指定目标路由,必填项。
      • 字符串形式:直接指定路径,如 to="/project-detail"
      • 对象形式:支持更复杂的路由配置(如携带参数、查询参数)
    • replace:设置为 true 时,跳转不会留下历史记录(类似 router.replace())。(无痕访问)
    • active-class:指定当前路由匹配时的激活样式类(默认是 router-link-active)。
    • exact:精确匹配路由(默认是模糊匹配)。

但是,不要这样做,因为我们不可能每加一个路由信息,都在这里维护对应的RouterLink代码。

当然如果想测试自己上面路由信息是否维护正确,可以用这个代码测试一下。

我们一般直接写 <RouterView/> ,Vue2中就是<router-view/>

image-20251204180304541

<RouterView/> 是路由匹配组件的占位符,用于渲染当前路由对应的组件。当路由切换时,<RouterView/> 会自动替换为对应的组件内容。

它会根据当前 URL 匹配的路由规则,自动渲染对应的组件。如果路由配置中有嵌套路由,内层的 <RouterView/> 会渲染嵌套的子组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div id="app">
<router-view/>
<!-- 全局消息通知组件,悬浮在右上角用户信息左侧 -->
<GlobalMessageNotification v-if="!isAuthPage" />
<!-- 全局用户信息组件,悬浮在右上角,但在登录相关页面不显示 -->
<GlobalUserProfile
v-if="!isAuthPage"
:floating="true"
:show-theme-toggle="!isAuthPage"
:is-dark-mode="isDarkMode"
@theme-toggle="handleThemeToggle"
/>
<!-- 悬浮消息提醒组件,在页面右侧 -->
<FloatingMessageReminder v-if="!isAuthPage" />
<!-- 全局错误弹窗 -->
<GlobalErrorDialog />
</div>
</template>

别忘了。js部分引入的模块也会有所变化

也就是说,我们需要先通过 app.use(router) 启用路由功能,然后才能在 App.vue 中用 <RouterView/> 展示路由对应的组件内容。这样,路由就正式启用了