vue3+vite+TS的axios二次封装和api请求

'# vue3+vite+TS的axios二次封装和api请求

一、背景与问题

在现代前端开发中,axios作为主流的HTTP请求库,其功能强大且灵活。但在实际项目中,直接使用axios存在诸多重复性工作:如统一的请求拦截、响应拦截、错误处理、请求头管理、超时控制等。对于大型项目来说,这种重复劳动会导致代码冗余和维护困难。

以一个典型场景为例:在开发一个电商系统时,每个API请求都需要携带token、设置Content-Type、处理超时、统一的错误提示。若不进行封装,每个请求都需要重复编写这些逻辑。此外,当需要支持接口mock测试时,还需要额外的处理逻辑。

二、基本原理

axios的二次封装核心在于以下三个层面:

  1. 请求拦截器:在请求发送前统一处理配置,如添加token、设置请求头、处理参数
  2. 响应拦截器:在收到响应后统一处理数据,如解析响应体、处理错误码
  3. 封装统一的请求方法:将基础请求方法抽象成可复用的接口,如get、post等

其底层原理基于axios的interceptors机制,通过注册拦截器函数来修改请求配置和响应数据。在TypeScript中,需要通过类型声明文件定义接口和类型,确保类型安全。

三、环境准备

创建Vite+Vue3+TypeScript项目:

npm create vue@latest
# 选择以下选项
? Project name: my-project
? UI framework: Vue 3
? Typescript: Yes
? CSS preprocessor: CSS
? Need ESLint: Yes
? Need Vitest: No

安装axios和相关依赖:

npm install axios

四、核心实现

1. 创建axios实例

// src/utils/axios.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';

// 定义请求拦截器类型
interface RequestInterceptors {
  requestIntercept: (config: AxiosRequestConfig) => AxiosRequestConfig;
  responseIntercept: (response: AxiosResponse) => AxiosResponse;
}

// 创建axios实例
const service: AxiosInstance = axios.create({
  baseURL: '/api', // 基础URL
  timeout: 10000,  // 超时时间
  withCredentials: true, // 是否发送跨域请求时携带cookie
});

// 请求拦截器
service.interceptors.request.use(
  (config: AxiosRequestConfig) => {
    // 1. 添加token到请求头
    const token = localStorage.getItem('token');
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`;
    }
    
    // 2. 设置Content-Type
    config.headers['Content-Type'] = 'application/json';
    
    // 3. 处理请求参数
    if (config.method === 'post' && config.data) {
      config.data = JSON.stringify(config.data);
    }
    
    return config;
  },
  (error: AxiosError) => {
    // 请求拦截错误处理
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  (response: AxiosResponse) => {
    // 1. 处理响应数据
    const { data } = response;
    
    // 2. 响应成功时的处理
    if (data.code === 200) {
      return data.data;
    }
    
    // 3. 响应失败时的处理
    return Promise.reject(data.message || '服务器响应异常');
  },
  (error: AxiosError) => {
    // 响应拦截错误处理
    if (error.response) {
      // 接收到服务器响应,但状态码不在2xx范围内
      console.error('响应错误:', error.response.status);
      return Promise.reject(error.response.data || '服务器响应异常');
    } else if (error.request) {
      // 没有收到响应
      console.error('请求未收到响应:', error.request);
      return Promise.reject('请求未收到响应');
    } else {
      // 请求设置错误
      console.error('请求设置错误:', error.message);
      return Promise.reject('请求设置错误');
    }
  }
);

export default service;

关键代码解释:

  • AxiosInstance类型声明确保类型安全
  • withCredentials设置为true支持跨域携带cookie
  • 请求拦截器处理token、Content-Type、参数序列化
  • 响应拦截器统一处理成功/失败响应,返回标准化数据

2. 封装统一的请求方法

// src/utils/axios.ts
// 继续上面的代码...

// 封装请求方法
export const request = <T>(config: AxiosRequestConfig): Promise<T> => {
  return new Promise((resolve, reject) => {
    service.request(config)
      .then((data: T) => {
        resolve(data);
      })
      .catch((error: AxiosError) => {
        reject(error.message);
      });
  });
};

// 封装get请求
export const get = <T>(url: string, params?: any): Promise<T> => {
  return request<T>({
    url,
    method: 'get',
    params
  });
};

// 封装post请求
export const post = <T>(url: string, data?: any): Promise<T> => {
  return request<T>({
    url,
    method: 'post',
    data
  });
};

关键代码解释:

  • request方法封装了通用请求逻辑
  • getpost方法作为快捷入口,简化调用
  • 使用泛型<T>确保类型安全

3. 错误处理与异常捕获

// src/utils/axios.ts
// 继续上面的代码...

// 错误处理函数
export const handleRequestError = (error: string) => {
  console.error('请求错误:', error);
  alert(`请求出错: ${error}`);
  return Promise.reject(error);
};

五、完整案例

创建一个登录功能的完整案例:

1. 前端代码

<!-- src/views/Login.vue -->
<template>
  <div class="login-container">
    <h2>用户登录</h2>
    <el-form :model="loginForm" label-width="80px" @submit.prevent="handleSubmit">
      <el-form-item label="用户名">
        <el-input v-model="loginForm.username" />
      </el-form-item>
      <el-form-item label="密码">
        <el-input v-model="loginForm.password" type="password" />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" native-type="submit">登录</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { post } from '@/utils/axios';

const loginForm = ref({
  username: '',
  password: ''
});

const handleSubmit = async () => {
  try {
    const response = await post('/login', {
      username: loginForm.value.username,
      password: loginForm.value.password
    });
    
    if (response) {
      alert('登录成功');
      // 保存token到本地存储
      localStorage.setItem('token', response.token);
    }
  } catch (error) {
    handleRequestError(error as string);
  }
};
</script>

2. 后端模拟接口(Node.js)

// server.js
const express = require('express');
const app = express();
const port = 3000;

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  
  // 简单验证逻辑
  if (username === 'admin' && password === '123456') {
    res.json({
      code: 200,
      message: '登录成功',
      data: {
        token: 'fake-token-123'
      }
    });
  } else {
    res.status(401).json({
      code: 401,
      message: '用户名或密码错误'
    });
  }
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

3. 运行效果

  1. 启动后端服务
  2. 启动前端开发服务器
  3. 在浏览器中访问登录页面
  4. 输入admin/123456登录
  5. 成功后会弹出"登录成功"提示,并保存token到localStorage

六、源码解析

1. 请求拦截器流程

service.interceptors.request.use(
  (config: AxiosRequestConfig) => {
    // 1. 添加token到请求头
    const token = localStorage.getItem('token');
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`;
    }
    
    // 2. 设置Content-Type
    config.headers['Content-Type'] = 'application/json';
    
    // 3. 处理请求参数
    if (config.method === 'post' && config.data) {
      config.data = JSON.stringify(config.data);
    }
    
    return config;
  },
  (error: AxiosError) => {
    return Promise.reject(error);
  }
);
  • 优先处理token,避免重复代码
  • 设置Content-Type确保服务器能正确解析
  • 对post请求进行参数序列化,避免浏览器自动处理

2. 响应拦截器处理

service.interceptors.response.use(
  (response: AxiosResponse) => {
    const { data } = response;
    
    if (data.code === 200) {
      return data.data;
    }
    
    return Promise.reject(data.message || '服务器响应异常');
  },
  (error: AxiosError) => {
    if (error.response) {
      console.error('响应错误:', error.response.status);
      return Promise.reject(error.response.data || '服务器响应异常');
    } else if (error.request) {
      console.error('请求未收到响应:', error.request);
      return Promise.reject('请求未收到响应');
    } else {
      console.error('请求设置错误:', error.message);
      return Promise.reject('请求设置错误');
    }
  }
);
  • 响应成功时返回data.data,处理服务器返回的业务数据
  • 响应失败时返回错误信息,统一处理错误提示
  • 区分不同类型的错误:服务器响应错误、请求未收到响应、请求设置错误

七、进阶使用

1. 跨域请求处理

// 配置axios实例
const service: AxiosInstance = axios.create({
  baseURL: '/api',
  timeout: 10000,
  withCredentials: true, // 允许携带cookie
});

在开发环境中,可以配置代理解决跨域问题:

// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': resolve(__dirname, './src')
    }
  },
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        pathRewrite: { '^/api': '' }
      }
    }
  }
});

2. 请求缓存优化

// 使用axios-cache-adapter
import axios from 'axios';
import { CacheAdapter } from 'axios-cache-adapter';

const cacheAdapter = new CacheAdapter({
  maxAge: 1000 * 60 * 10, // 10分钟
  maxSize: 1000
});

const service: AxiosInstance = axios.create({
  baseURL: '/api',
  timeout: 10000,
}).use(cacheAdapter);

3. 并发请求处理

// 使用axios-concurrent库
import axios from 'axios';
import { concurrent } from 'axios-concurrent';

const service = axios.create({
  baseURL: '/api',
  timeout: 10000,
});

const concurrentService = concurrent(service, {
  maxConcurrent: 5, // 最大并发数
  maxQueue: 100 // 最大队列长度
});

八、性能与工程实践

1. 性能优化策略

  1. 请求缓存:对不常变化的接口使用缓存,减少服务器压力
  2. 并发控制:使用axios-concurrent限制同时进行的请求数量
  3. 响应压缩:在服务器端启用Gzip压缩
  4. 减少请求次数:合并多个接口请求,避免多次往返
  5. 预加载策略:对高频访问的接口进行预加载

2. 异常处理规范

// 统一错误处理
export const handleRequestError = (error: string) => {
  console.error('请求错误:', error);
  alert(`请求出错: ${error}`);
  
  // 记录错误日志
  if (process.env.NODE_ENV === 'production') {
    // 发送错误日志到服务器
    // logger.error(error);
  }
  
  return Promise.reject(error);
};

3. 安全风险分析

  1. token安全

    • 使用HTTPS传输
    • 设置secure和httpOnly标志
    • 设置较短的token有效期
    • 使用刷新token机制
  2. CSRF防护

    • 在服务器端验证XSRF-TOKEN
    • 使用withCredentials设置为true时需要处理
    • 使用JWT替代传统session机制
  3. 数据安全

    • 使用HTTPS加密传输
    • 对敏感数据进行加密处理
    • 设置Content-Security-Policy头

九、常见问题与踩坑

1. 跨域问题

错误示例

// 前端代码
axios.get('http://localhost:3000/api/user');

解决方法

  • 使用vite的代理配置
  • 使用CORS中间件
  • 使用反向代理服务器

2. 拦截器顺序错误

错误示例

service.interceptors.response.use(
  (response) => { /* 响应拦截器 */ },
  (error) => { /* 错误处理 */ }
);

service.interceptors.request.use(
  (config) => { /* 请求拦截器 */ },
  (error) => { /* 错误处理 */ }
);

正确顺序

// 先注册请求拦截器
service.interceptors.request.use(...);
// 再注册响应拦截器
service.interceptors.response.use(...);

3. 类型定义不准确

错误示例

// 响应拦截器中未处理错误码
if (data.code === 200) {
  return data.data;
}

改进方法

// 增加类型声明
interface ResponseData<T> {
  code: number;
  message: string;
  data: T;
}

// 响应拦截器处理
if (data.code === 200) {
  return data.data;
}

4. 错误处理不完整

错误示例

try {
  await post('/login', { username, password });
} catch (error) {
  console.error(error);
}

改进方法

try {
  await post('/login', { username, password });
} catch (error) {
  handleRequestError(error as string);
}

十、最佳实践

1. 推荐方案

  1. 统一的请求封装:所有API请求都通过封装后的request方法发起
  2. 类型安全:使用TypeScript定义接口和类型,确保类型安全
  3. 错误处理统一:所有错误都通过统一的handleRequestError处理
  4. 请求拦截器:统一处理token、Content-Type等通用配置
  5. 响应拦截器:统一处理成功/失败响应,返回标准化数据

2. 适用场景

  1. 中大型项目需要统一管理请求
  2. 需要统一的错误处理和提示
  3. 需要处理跨域、token、超时等通用需求
  4. 需要支持接口mock测试时

3. 不适用场景

  1. 非常小的项目,简单请求无需封装
  2. 需要高度定制化请求逻辑的特殊场景
  3. 需要实时性要求极高的场景
  4. 需要处理特殊格式数据(如二进制文件)的场景

十一、总结

本文深入探讨了vue3+vite+TS项目中axios的二次封装实现,从原理到实践,从基础到进阶,全面解析了其工作原理和使用方法。通过三个代码示例展示了拦截器配置、请求封装和错误处理的实现,提供了一个完整的登录案例演示了如何在实际项目中使用。

在实际开发中,二次封装可以显著提升开发效率和代码可维护性,但也要注意其适用场景。对于复杂项目,建议采用分模块、分功能的封装策略,结合接口管理工具,形成统一的请求规范。同时,要关注安全性、性能优化和错误处理,确保系统的稳定运行。

在开发过程中,需要注意常见的陷阱:如拦截器顺序、类型定义、错误处理等,这些都是容易犯的错误。通过本文的分析和示例,希望能够帮助开发者避免这些常见问题,写出更健壮、可维护的前端代码。

VUE , ios
最后修改于:2026年09月16日 20:27

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日