前端工程化实践:Axios 通用请求方法封装

2026-03-07 66 浏览 0 评论

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

一、封装前的准备工作

在开始封装前,我们需要明确两个核心依赖文件的作用(代码中已引入):

  • config.js :存储项目通用配置,如接口基础域名 APIURL 、签名密钥 secretKey 等;
  • methods.js :封装通用工具方法,如 getUrlParam (获取 URL 参数)、 LSGet (从本地存储取值)、 createSignature (生成接口签名)等。

同时,我们需要定义两个全局标记变量,避免重复弹出相同提示弹窗:

// 标记,是否已经弹出需要登录的弹窗
let isPopupNeedLogin = false;
// 标记,是否已经弹出网络错误弹窗
let isPopupLostNet = false;

二、响应拦截器优化:统一返回数据格式

axios 原生响应结果包含 configheadersdata 等多层结构,直接使用需层层解构。通过响应拦截器,我们可以统一返回接口核心数据,并处理无响应的异常场景:

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);
  }
);

核心作用

  1. 简化成功响应的取值逻辑,无需每次写 res.data
  2. 统一失败响应的处理入口,区分「有响应的业务错误」和「无响应的网络错误」。

三、核心请求方法封装: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; // 弹窗关闭后重置标记
          },
        });
      }
    });
}

核心亮点

  1. 数据格式标准化:将后端可能返回的 msg / httpCode / errsInfo 统一为 message / code ,前端无需适配多种字段名;
  2. 鉴权失效处理:通过标记变量避免重复弹窗,点击后跳转小程序登录页(适配 H5 嵌套小程序场景);
  3. 网络错误兜底:统一提示文案,且弹窗关闭后重置标记,保证后续请求可正常触发提示;
  4. 回调函数统一返回:无论成功/失败,都通过 cb 函数返回数据,前端调用时逻辑统一。

四、使用示例

封装完成后,前端调用接口的方式将极度简化,示例如下:

// 调用用户信息接口
request(
  '/user/info', // 接口路径
  'get', // 请求方法
  { userId: 123 }, // 请求参数
  (res) => { // 回调函数
    if (res.code === 200) {
      // 处理成功逻辑
      console.log('用户信息:', res.data);
    } else {
      // 处理业务错误
      console.error('请求失败:', res.message);
    }
  }
);

五、扩展建议

  1. 请求拦截器 :可添加请求拦截器,统一处理请求加载中状态(如显示 loading);
  2. 取消重复请求 :针对频繁触发的请求(如搜索),可添加取消重复请求的逻辑;
  3. 请求重试 :对网络波动导致的失败请求,可添加重试机制(如最多重试 2 次);
  4. 类型定义 :在 TypeScript 项目中,可为 request 函数和响应数据添加类型注解,提升代码健壮性;
  5. 日志上报 :可在 catch 中添加错误日志上报逻辑,便于线上问题排查。

总结

  1. 本次封装通过 响应拦截器 简化了响应数据结构,通过 全局标记变量 避免重复弹窗,提升了用户体验;
  2. 核心 request 函数实现了 通用参数注入签名生成参数适配数据格式标准化错误兜底 等能力,统一了请求逻辑;
  3. 封装后的请求方法具备高复用性和扩展性,前端调用接口时只需关注「路径、参数、业务逻辑」,无需重复处理鉴权、错误提示等通用逻辑。

这套封装方案适配了 H5 嵌套小程序的场景,也可根据实际项目需求调整(如去掉小程序相关逻辑、修改鉴权方式等),是前端工程化中请求层的通用实践方案。


发布评论

发布评论前请先 登录
取消
0 评论
点赞
收藏

评论列表 0

暂无评论