前端工程化实践:Axios 通用请求方法封装
在前端开发中,HTTP 请求是与后端交互的核心环节。直接使用 axios 原生方法会导致代码冗余、错误处理不统一、鉴权逻辑分散等问题。本文将详细拆解一套可复用、高扩展性的 axios 通用请求方法封装方案,帮助开发者规范请求逻辑、提升代码可维护性。

一、封装前的准备工作
在开始封装前,我们需要明确两个核心依赖文件的作用(代码中已引入):
config.js:存储项目通用配置,如接口基础域名APIURL、签名密钥secretKey等;methods.js:封装通用工具方法,如getUrlParam(获取 URL 参数)、LSGet(从本地存储取值)、createSignature(生成接口签名)等。
同时,我们需要定义两个全局标记变量,避免重复弹出相同提示弹窗:
// 标记,是否已经弹出需要登录的弹窗
let isPopupNeedLogin = false;
// 标记,是否已经弹出网络错误弹窗
let isPopupLostNet = false;二、响应拦截器优化:统一返回数据格式
axios 原生响应结果包含 config 、 headers 、 data 等多层结构,直接使用需层层解构。通过响应拦截器,我们可以统一返回接口核心数据,并处理无响应的异常场景:
axios.interceptors.response.use(
// 成功响应:直接返回接口返回的 data 字段,简化后续取值
res => res.data,
// 失败响应:分情况处理
err => {
// 有响应但状态码非 2xx:返回响应体中的 data(便于后续统一处理业务错误)
if (err.response) return Promise.resolve(err.response.data);
// 无响应(如网络中断):抛出错误,交由 catch 处理
else return Promise.reject(err);
}
);核心作用 :
- 简化成功响应的取值逻辑,无需每次写
res.data; - 统一失败响应的处理入口,区分「有响应的业务错误」和「无响应的网络错误」。
三、核心请求方法封装:request 函数
我们将所有请求逻辑封装到 request 函数中,对外暴露统一的调用入口,函数参数设计如下:
pathname:接口路径(如/user/info);method:请求方法(如get/post);params:请求参数;cb:请求完成后的回调函数(默认空函数,避免未传参时报错)。
3.1 基础参数处理:注入通用参数
首先为所有请求注入固定参数,确保接口调用的环境一致性:
export default function request(pathname, method, params, cb = () => {}) {
// 注入来源标识:标记为 H5 端请求
params.from = 'h5';
// 注入环境版本:从 URL 参数中获取(适配小程序/公众号等多环境)
params.envVersion = methods.getUrlParam('envVersion');
// 从本地存储获取用户 token(鉴权用)
let token = methods.LSGet('token');
// 拼接完整请求地址:支持参数中自定义域名,无则使用配置文件中的基础域名
let url = (params.url || config.APIURL) + pathname;
// 移除自定义域名参数(避免传递给后端)
delete params.url;
// 构建 axios 基础配置
let axiosConfig = {
url, // 完整请求地址
method, // 请求方法
headers: { Authorization: ` ${token}` }, // 鉴权头(Bearer Token 格式)
responseType: 'json', // 响应数据格式
};关键说明 :
params.from:后端可通过该参数区分请求来源(如 H5/小程序/APP);params.envVersion:适配多环境(如开发/测试/生产);- 支持自定义请求域名:灵活应对不同接口的域名差异。
3.2 签名生成:接口安全防护
为防止接口被篡改,需为请求参数生成签名,步骤如下:
// 生成签名:添加时间戳和随机数,提升签名唯一性
params.timestamp = new Date().getTime(); // 时间戳
params.nonce = 'h5' + Math.floor(Math.random() * 1000000000); // 随机数(前缀标识 H5 端)
delete params.signature; // 清空可能存在的旧签名
// 过滤无效参数:删除 undefined/null/NaN 的参数,避免签名计算错误
for (let key in params) {
if (typeof params[key] == 'undefined' || params[key] == null || Number.isNaN(params[key])) {
delete params[key];
}
}
// 生成签名:使用配置文件中的密钥,通过工具方法生成
params.signature = methods.createSignature(params, config.secretKey);安全要点 :
- 时间戳 + 随机数:避免重复签名,防止重放攻击;
- 过滤无效参数:保证签名计算的准确性;
- 签名密钥存储在配置文件:便于统一管理和环境切换。
3.3 请求参数适配:区分 GET/POST
根据请求方法不同,将参数放入对应位置(GET 放 params,POST 放 data):
// GET 请求:参数拼接到 URL 上
if (method == 'post') axiosConfig.data = params;
// POST 请求:参数放在请求体中
else axiosConfig.params = params;3.4 响应处理:统一业务逻辑和错误提示
请求发送后,统一处理响应结果,包括数据格式标准化、鉴权失效、网络错误等场景:
axios(axiosConfig)
.then(res => {
// 数据格式标准化:统一字段名,降低前端适配成本
// 1. 将 msg 字段统一为 message
if ('msg' in res) {
res.message = res.msg;
delete res.msg;
}
// 2. 将 httpCode 字段统一为 code
if (!('code' in res) && 'httpCode' in res) {
res.code = res.httpCode;
delete res.httpCode;
}
// 3. 将 errsInfo 字段统一为 message
if ('errsInfo' in res) {
res.message = res.errsInfo;
delete res.errsInfo;
}
// 鉴权失效处理:401(token 过期)/402(token 无效)
if (res.code == 401 || res.code == 402) {
// 避免重复弹出登录弹窗
if (!isPopupNeedLogin) {
isPopupNeedLogin = true;
const buttonText = res.code == 401 ? '重新登录' : '确定';
// 弹出确认弹窗(需结合项目的弹窗组件,如 vant/element)
this.confirm({
content: res.message, // 提示文案(后端返回)
confirmText: buttonText, // 按钮文字
success: () => {
// 跳转至小程序登录页(适配 H5 嵌套小程序场景)
wx.miniProgram.navigateTo('');
isPopupNeedLogin = false; // 弹窗关闭后重置标记
},
});
}
}
// 网络恢复:如果之前弹出了网络错误弹窗,关闭它
if (isPopupLostNet) this.confirmClose();
// 执行回调函数,返回处理后的响应数据
cb(res);
})
.catch(err => {
// 网络错误处理:如断网、跨域等
console.log('wjRequest error', err);
// 统一返回错误格式,便于前端页面处理
cb({ success: false, data: err.data, message: '网络不给力,请稍后重试', code: 500 });
// 避免重复弹出网络错误弹窗
if (!isPopupLostNet) {
isPopupLostNet = true;
this.confirm({
content: '网络不给力,请稍后重试',
showCancel: false, // 仅显示确认按钮
success: () => {
isPopupLostNet = false; // 弹窗关闭后重置标记
},
});
}
});
}核心亮点 :
- 数据格式标准化:将后端可能返回的
msg/httpCode/errsInfo统一为message/code,前端无需适配多种字段名; - 鉴权失效处理:通过标记变量避免重复弹窗,点击后跳转小程序登录页(适配 H5 嵌套小程序场景);
- 网络错误兜底:统一提示文案,且弹窗关闭后重置标记,保证后续请求可正常触发提示;
- 回调函数统一返回:无论成功/失败,都通过
cb函数返回数据,前端调用时逻辑统一。
四、使用示例
封装完成后,前端调用接口的方式将极度简化,示例如下:
// 调用用户信息接口
request(
'/user/info', // 接口路径
'get', // 请求方法
{ userId: 123 }, // 请求参数
(res) => { // 回调函数
if (res.code === 200) {
// 处理成功逻辑
console.log('用户信息:', res.data);
} else {
// 处理业务错误
console.error('请求失败:', res.message);
}
}
);五、扩展建议
- 请求拦截器 :可添加请求拦截器,统一处理请求加载中状态(如显示 loading);
- 取消重复请求 :针对频繁触发的请求(如搜索),可添加取消重复请求的逻辑;
- 请求重试 :对网络波动导致的失败请求,可添加重试机制(如最多重试 2 次);
- 类型定义 :在 TypeScript 项目中,可为
request函数和响应数据添加类型注解,提升代码健壮性; - 日志上报 :可在
catch中添加错误日志上报逻辑,便于线上问题排查。
总结
- 本次封装通过 响应拦截器 简化了响应数据结构,通过 全局标记变量 避免重复弹窗,提升了用户体验;
- 核心
request函数实现了 通用参数注入 、 签名生成 、 参数适配 、 数据格式标准化 、 错误兜底 等能力,统一了请求逻辑; - 封装后的请求方法具备高复用性和扩展性,前端调用接口时只需关注「路径、参数、业务逻辑」,无需重复处理鉴权、错误提示等通用逻辑。
这套封装方案适配了 H5 嵌套小程序的场景,也可根据实际项目需求调整(如去掉小程序相关逻辑、修改鉴权方式等),是前端工程化中请求层的通用实践方案。




