Vue3企业级项目网络请求封装:分层架构与Mock服务搭建
在vue3企业级项目里,网络请求层的设计是个基础活,也是个核心活。一个好的请求架构,能让项目开发顺溜不少,后期维护也省心。
我打算把Vue3网络请求的优雅封装讲清楚,包括架构设计、Mock服务、Axios拦截器、取消请求、防重、重试、useRequest封装这些内容。
技术选型:用Axios还是Alova
做网络请求封装,先得选个请求库。
Axios大家最熟,生态成熟,用着简单,企业项目里用得最多,这次肯定选它。
顺便提一下Alova,这库功能挺强,状态管理、请求缓存都有优势,设计思路也值得学。但我只在个人项目里试过,没在生产落地过,所以系列还是用Axios,保证方案实用、能落地。
前期准备:标准化目录与分层设计
写代码前先把目录结构搭好,这是分层架构的基础,后面代码也好组织。
1. 标准化目录结构
wumeng-vue3-app-template/
|- mock/ # Mock服务目录,和src平级,放接口模拟文件
| |- demo.ts # 示例Mock接口
|- src/
| |- http/ # 网络请求核心目录
| | |- core/ # 通用请求封装层,和业务无关
| | | |- http-client.ts # Axios实例核心封装
| | | |- interceptors.ts # 拦截器封装
| | | |- index.ts # 通用能力统一导出
| | | |- types.ts # 通用类型定义
| | |- index.ts # 项目配置层,组装core能力,配项目专属参数
| |- services/ # 业务请求层(以前叫api层)
| | |- base-service.ts # 通用CRUD封装,业务模块可继承2. 四层核心设计思路
整个架构分四层,每层干自己的事,耦合度低,好复用、好扩展、好维护。调用关系是:业务请求层 → 项目配置层 → 通用请求封装层 → Axios原生库。
Mock服务层:根目录下mock/,独立于业务代码,专门模拟后端接口。以后也可以换成SpringBoot、NestJS这些真实后端。
通用请求封装层:src/http/core/,核心部分。对Axios做底层封装,拦截器、取消请求、防重、重试这些通用功能都放这,每个功能独立成类。这层代码和具体业务没关系,可以抽成独立工具库,多个项目复用。
项目配置层:src/http/index.ts,项目自定义入口。组装core层的通用能力,配项目专属的请求参数(比如超时时间、基础路径)、拦截器逻辑、错误处理方式,最后导出适配当前项目的Axios实例和CRUD函数。
业务请求层:src/services/,对应以前的api目录。和具体业务绑死,定义各业务模块的接口。抽了个base-service.ts实现通用CRUD,各业务service直接继承,少写重复代码。
层级依赖关系长这样:
+---------------------+
| service层 (业务API定义) |
+---------------------+
↑ 调用
+---------------------+
| 项目配置层 (项目自定义) |
+---------------------+
↑ 实例化
+---------------------+
| 通用请求封装层 (通用能力) |
+---------------------+
↑ 依赖
+---------------------+
| Axios 原生库 |
+---------------------+3. 通用类型定义
和后端打交道,统一的类型定义能保证TypeScript类型安全,前后端交互格式也规范。在src/http/core/types.ts里定义三个基础通用类型,覆盖通用响应、分页请求、分页响应三个高频场景,用泛型适配不同业务数据。
/**
* 通用响应结构,适配大多数企业级项目的接口返回格式
*/
export interface ApiResp<T = any> {
code: number
message: string
data: T
}
/**
* 分页请求结构,标准化分页入参
*/
export interface PageReq {
pageNum: number
pageSize: number
}
/**
* 分页响应数据结构,适配通用响应的data字段
*/
export interface PageData<T> {
list: T[]
total: number
}在src/http/core/index.ts里统一导出所有类型,方便其他地方按需引入:
export * from './types'关于通用响应结构的设计争议
企业项目里,通用响应结构有两种主流设计,各有各的好,看团队习惯和项目需求选:
思路一:不管请求成功失败,都返回code/data/message标准结构。这样HTTP请求错误(比如404、500)和业务错误(比如参数不对、没权限)分开处理,前端解析时先看HTTP状态码,再看业务code码。适合大项目,错误处理精细。
思路二:请求成功直接返回业务数据,业务失败才返回标准结构。这样两类错误合并处理,前端只要HTTP状态码是200就算业务成功,开发简单。适合中小项目或快速开发。
我个人更倾向第二种,但多年团队开发经验看,大部分开发者习惯第一种,所以系列也按第一种思路来封装。
快速落地:基于MockJS+Vite搭Mock服务
后端接口还没开发完的时候,Mock服务能模拟接口返回数据,让前端开发不依赖后端,前后端能并行开发。这次用MockJS生成模拟数据,配合vite-plugin-mock插件快速搭Mock服务,这插件支持热更新、TypeScript友好,是Vite项目的主流Mock方案。
1. 安装依赖
用pnpm装核心依赖,@types/mockjs给MockJS加TypeScript类型支持,版本选生产验证过的稳定版:
pnpm add mockjs @types/mockjs vite-plugin-mock -D
# 版本说明:mockjs@1.1.0、vite-plugin-mock@3.0.22. 配置Vite插件
在项目根目录vite.config.ts里引入并配vite-plugin-mock,指定Mock文件目录,开Mock服务:
// 省略其他导入
import { viteMockServe } from 'vite-plugin-mock'
export default defineConfig({
plugins: [
// 其他插件(比如vue())
viteMockServe({
mockPath: './mock', // 指定Mock文件放哪
enable: true, // 开Mock服务
}),
],
// 其他Vite配置
})3. 写Mock接口实现CRUD
在mock目录下建demo.ts,用MockJS生成模拟数据,实现一套标准CRUD Mock接口,适配前面定义的ApiResp通用响应结构,同时支持分页、关键词搜索这些高频功能。
import Mock from 'mockjs'
import { ApiResp } from '../src/http/core'
import { MockMethod } from 'vite-plugin-mock'
// 生成100条模拟测试数据
const demoList = Mock.mock({
'list|100': [
{
'id|+1': 1,
title: '@ctitle(5, 10)', // 随机生成中文标题
content: '@cparagraph(1, 3)', // 随机生成中文段落
author: '@name', // 随机生成姓名
status: '@boolean', // 随机生成布尔值
createdAt: '@datetime', // 随机生成时间
updatedAt: '@datetime',
},
],
}).list
// 封装成功响应
function success<T>(data: T): ApiResp<T> {
return {
code: 0,
message: 'success',
data,
}
}
// 封装失败响应
function error(message: string, code: number = 500): ApiResp<null> {
return {
code,
message,
data: null,
}
}
// 定义Mock接口
const demoMock: MockMethod[] = [
// 分页查询列表,支持关键词搜索
{
url: '/api/demo',
method: 'get',
timeout: 1000, // 模拟网络延迟
response: ({ query }) => {
const pageNum = parseInt(query.pageNum) || 1
const pageSize = parseInt(query.pageSize) || 10
const keyword = query.keyword || ''
// 关键词过滤
let filteredList = demoList
if (keyword) {
filteredList = demoList.filter(
(item) =>
item.title.includes(keyword) ||
item.content.includes(keyword) ||
item.author.includes(keyword)
)
}
// 分页处理
const start = (pageNum - 1) * pageSize
const end = start + pageSize
const list = filteredList.slice(start, end)
return success({ list, total: filteredList.length })
},
},
// 根据ID查询详情
{
url: '/api/demo/:id',
method: 'get',
timeout: 3000,
response: ({ query }) => {
const item = demoList.find((item) => item.id === parseInt(query.id))
return item ? success(item) : error('Item not found')
},
},
// 新增数据
{
url: '/api/demo',
method: 'post',
response: ({ body }) => {
const newItem = {
id: demoList.length + 1,
...body,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
demoList.push(newItem)
return success(newItem)
},
},
// 根据ID修改数据
{
url: '/api/demo/:id',
method: 'put',
response: ({ query, body }) => {
const index = demoList.findIndex((item) => item.id === parseInt(query.id))
if (index !== -1) {
demoList[index] = {
...demoList[index],
...body,
updatedAt: new Date().toISOString(),
}
return success(demoList[index])
}
return error('Item not found')
},
},
// 根据ID删除数据
{
url: '/api/demo/:id',
method: 'delete',
response: ({ query }) => {
const index = demoList.findIndex((item) => item.id === parseInt(query.id))
if (index !== -1) {
demoList.splice(index, 1)
return success(null)
}
return error('Item not found')
},
},
]
export default demoMock4. 验证Mock服务
启动Vite项目,浏览器访问http://localhost:5173/api/demo,要是能看到带code/message/data的JSON数据,且data里有分页的list和total字段,说明Mock服务搭好了。
写在最后
完成了Vue3网络请求封装的分层架构设计和Mock服务落地,这是后面所有功能的基础。核心思想是通过分层解耦,让通用请求能力和业务代码分开,保证代码好复用、好维护。
本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!