axios 请求头封装过程遵循「最小可用 → 逐步增强」

步骤 1:极简版 - 基础实例 + 默认请求头

这是最基础的封装,仅创建 axios 实例并配置固定默认请求头,满足最基本的请求头需求,适合新手入门。

1.1 安装依赖

bash 复制代码
npm install axios
# 或 yarn add axios

1.2 基础封装代码(src/utils/request.ts)

javascript 复制代码
// 第一步:仅创建axios实例 + 固定默认请求头
import axios from 'axios';

// 1. 创建axios实例,配置基础请求头
const request = axios.create({
  baseURL: 'http://localhost:3000/api', // 接口基础地址(临时写死)
  timeout: 10000, // 请求超时时间
  // 核心:默认请求头配置(所有请求都会携带)
  headers: {
    'Content-Type': 'application/json;charset=utf-8', // 默认JSON格式
    'X-Platform': 'web' // 自定义固定请求头(如平台标识)
  }
});

// 导出实例,直接使用
export default request;

1.3 组件中简单使用

xml 复制代码
<script setup lang="ts">
import request from '@/utils/request';

// 发起请求(自动携带默认请求头)
const fetchData = async () => {
  try {
    const res = await request.get('/user/list');
    console.log(res);
  } catch (err) {
    console.error(err);
  }
};
</script>

1.4 关键解释

  • axios.create():创建独立的 axios 实例,避免污染全局 axios 配置;
  • headers 配置项:设置所有请求默认携带 的固定请求头,比如 Content-Type 是最常用的默认头;
  • 此版本仅满足「固定请求头」需求,无法动态修改(如 Token)。

步骤 2:基础增强 - 动态请求头(Token)+ 请求拦截器

这是实际项目中最核心的需求:根据登录状态动态添加 Token 到请求头,通过请求拦截器实现。

2.1 升级封装代码(src/utils/request.ts)

javascript 复制代码
// 第二步:新增请求拦截器,动态添加Token
import axios, { AxiosRequestConfig, AxiosError } from 'axios';

const request = axios.create({
  baseURL: 'http://localhost:3000/api',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json;charset=utf-8',
    'X-Platform': 'web'
  }
});

// 核心新增:请求拦截器(请求发送前执行,用于修改请求头)
request.interceptors.request.use(
  // 成功回调:修改请求配置(重点是headers)
  (config: AxiosRequestConfig) => {
    // 1. 从本地存储获取Token(登录后存入)
    const token = localStorage.getItem('token');
    // 2. 如果有Token,动态添加到请求头(后端常用Authorization字段)
    if (token) {
      // 注意:TypeScript需先判断headers存在,避免类型报错
      config.headers = config.headers || {};
      config.headers.Authorization = `Bearer ${token}`; // 按后端格式拼接
    }
    return config;
  },
  // 失败回调:捕获请求配置错误
  (error: AxiosError) => {
    console.error('请求头配置失败:', error);
    return Promise.reject(error);
  }
);

export default request;

2.2 组件使用(登录后自动携带 Token)

xml 复制代码
<script setup lang="ts">
import request from '@/utils/request';

// 模拟登录:登录成功后存储Token
const login = async () => {
  const res = await request.post('/login', { username: 'test', password: '123456' });
  // 存储Token到本地
  localStorage.setItem('token', res.data.token);
};

// 登录后发起请求:自动携带Token请求头
const fetchUserInfo = async () => {
  const res = await request.get('/user/info');
  console.log(res);
};
</script>

2.3 关键解释

  • request.interceptors.request.use():请求拦截器是「动态修改请求头」的核心,请求发送前会执行此回调;
  • Token 处理逻辑:登录后将 Token 存入 localStorage,后续所有请求都会通过拦截器自动添加到 Authorization 头;
  • TypeScript 注意:config.headers 可能为 undefined,需先赋值避免类型报错。

步骤 3:TS 强化 - 类型约束 + 统一响应格式

在 Vue + TS 项目中,需要完善类型定义,避免 any 类型,保证代码的类型安全。

3.1 升级封装代码(src/utils/request.ts)

typescript 复制代码
// 第三步:完善TypeScript类型约束
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';

// 新增1:定义接口返回数据的通用类型(和后端约定)
interface ApiResponse<T = any> {
  code: number; // 业务状态码(如200成功,401未登录)
  message: string; // 提示信息
  data: T; // 实际返回数据
}

// 新增2:扩展axios配置类型(支持自定义业务配置,比如是否需要Token)
interface CustomRequestConfig extends AxiosRequestConfig {
  needToken?: boolean; // 自定义配置:是否需要携带Token(默认true)
}

// 创建实例时指定类型
const request: AxiosInstance = axios.create({
  baseURL: 'http://localhost:3000/api',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json;charset=utf-8',
    'X-Platform': 'web'
  }
});

// 拦截器升级:使用自定义配置类型
request.interceptors.request.use(
  (config: CustomRequestConfig) => {
    // 新增:根据needToken配置决定是否携带Token
    if (config.needToken !== false) {
      const token = localStorage.getItem('token');
      if (token) {
        config.headers = config.headers || {};
        config.headers.Authorization = `Bearer ${token}`;
      }
    }
    return config;
  },
  (error: AxiosError) => {
    return Promise.reject(error);
  }
);

// 新增:响应拦截器(统一处理返回结果,强化类型)
request.interceptors.response.use(
  (response: AxiosResponse<ApiResponse>) => {
    const res = response.data;
    // 统一业务码校验(比如401未登录)
    if (res.code !== 200) {
      if (res.code === 401) {
        console.error('Token过期,请重新登录');
        // 可在此处跳转登录页
        localStorage.removeItem('token');
      }
      return Promise.reject(new Error(res.message || '请求失败'));
    }
    return res; // 直接返回处理后的data,简化组件使用
  },
  (error: AxiosError) => {
    console.error('请求失败:', error.message);
    return Promise.reject(error);
  }
);

export default request;

3.2 组件使用(带类型提示)

typescript 复制代码
<script setup lang="ts">
import request from '@/utils/request';

// 定义返回数据的具体类型
interface UserInfo {
  id: number;
  name: string;
  age: number;
}

// 1. 带Token的请求(默认)
const fetchUserInfo = async () => {
  try {
    // 泛型指定返回数据类型,获得TS类型提示
    const res = await request.get<ApiResponse<UserInfo>>('/user/info');
    console.log(res.data.name); // TS会提示name属性
  } catch (err) {
    console.error(err);
  }
};

// 2. 不需要Token的公开接口(手动关闭)
const fetchPublicData = async () => {
  const res = await request.get('/public/data', {
    needToken: false // 自定义配置:不携带Token
  });
  console.log(res);
};
</script>

3.3 关键解释

  • ApiResponse:统一接口返回类型,避免组件中使用 any,TS 会提示 code/message/data 字段;
  • CustomRequestConfig:扩展 axios 原生配置,支持自定义业务配置(如 needToken),灵活控制是否携带 Token;
  • 响应拦截器:统一处理业务码(如 401 Token 过期),简化组件中的错误处理。

步骤 4:易用性提升 - 封装通用请求方法

封装 get/post 等通用方法,简化组件调用,进一步强化 TypeScript 泛型支持。

4.1 升级封装代码(src/utils/request.ts)

typescript 复制代码
// 第四步:封装get/post通用方法,简化使用
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';

// 复用步骤3的类型定义
interface ApiResponse<T = any> {
  code: number;
  message: string;
  data: T;
}

interface CustomRequestConfig extends AxiosRequestConfig {
  needToken?: boolean;
}

const request: AxiosInstance = axios.create({
  baseURL: 'http://localhost:3000/api',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json;charset=utf-8',
    'X-Platform': 'web'
  }
});

// 复用步骤3的拦截器逻辑
request.interceptors.request.use(
  (config: CustomRequestConfig) => {
    if (config.needToken !== false) {
      const token = localStorage.getItem('token');
      if (token) {
        config.headers = config.headers || {};
        config.headers.Authorization = `Bearer ${token}`;
      }
    }
    return config;
  },
  (error: AxiosError) => Promise.reject(error)
);

request.interceptors.response.use(
  (response: AxiosResponse<ApiResponse>) => {
    const res = response.data;
    if (res.code !== 200) {
      if (res.code === 401) {
        localStorage.removeItem('token');
        console.error('请重新登录');
      }
      return Promise.reject(new Error(res.message));
    }
    return res;
  },
  (error: AxiosError) => Promise.reject(error)
);

// 核心新增:封装通用GET方法
export const get = <T = any>(
  url: string,
  params?: Record<string, any>, // URL参数类型
  config?: CustomRequestConfig // 自定义配置
): Promise<ApiResponse<T>> => {
  return request.get(url, { params, ...config });
};

// 封装通用POST方法
export const post = <T = any>(
  url: string,
  data?: Record<string, any>, // 请求体数据类型
  config?: CustomRequestConfig
): Promise<ApiResponse<T>> => {
  return request.post(url, data, config);
};

// 导出封装方法(组件优先用get/post,而非直接用request)
export default request;

4.2 组件使用(更简洁)

typescript 复制代码
<script setup lang="ts">
import { get, post } from '@/utils/request';

interface UserInfo {
  id: number;
  name: string;
}

// 使用封装的get方法(参数更清晰,泛型提示)
const fetchUser = async () => {
  const res = await get<UserInfo>('/user/info', { id: 1 });
  console.log(res.data.name);
};

// 使用封装的post方法
const submitForm = async () => {
  const res = await post<{ success: boolean }>('/user/save', { name: '张三' });
  console.log(res.data.success);
};
</script>

4.3 关键解释

  • 封装 get/post 方法:将「URL、参数、配置」拆分,参数更清晰,符合日常开发习惯;
  • 泛型支持:方法级别的泛型 <T> 让组件能精准指定返回数据类型,TS 提示更友好;
  • 组件无需关注 axios 原生调用方式(如 get 的参数是 { params }),降低使用成本。

步骤 5:进阶优化 - 自动适配 Content-Type + 环境变量

解决「表单 / 文件上传」场景的请求头适配,同时通过环境变量隔离不同环境的接口地址。

5.1 升级封装代码(src/utils/request.ts)

typescript 复制代码
// 第五步:进阶优化(Content-Type自动适配 + 环境变量)
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';

interface ApiResponse<T = any> {
  code: number;
  message: string;
  data: T;
}

interface CustomRequestConfig extends AxiosRequestConfig {
  needToken?: boolean;
}

// 核心修改1:从环境变量读取baseURL(不再写死)
const request: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL, // Vite环境变量(需以VITE_开头)
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json;charset=utf-8',
    'X-Platform': 'web'
  }
});

request.interceptors.request.use(
  (config: CustomRequestConfig) => {
    // 1. Token逻辑(复用)
    if (config.needToken !== false) {
      const token = localStorage.getItem('token');
      if (token) {
        config.headers = config.headers || {};
        config.headers.Authorization = `Bearer ${token}`;
      }
    }

    // 核心新增2:自动适配Content-Type(表单/文件上传场景)
    if (config.data instanceof FormData) {
      // 文件上传时,自动修改为multipart/form-data(无需手动设置)
      config.headers['Content-Type'] = 'multipart/form-data';
    }

    return config;
  },
  (error: AxiosError) => Promise.reject(error)
);

request.interceptors.response.use(
  (response: AxiosResponse<ApiResponse>) => {
    const res = response.data;
    if (res.code !== 200) {
      if (res.code === 401) {
        localStorage.removeItem('token');
        console.error('Token过期,请重新登录');
      }
      return Promise.reject(new Error(res.message));
    }
    return res;
  },
  (error: AxiosError) => Promise.reject(error)
);

// 封装get/post(复用)
export const get = <T = any>(
  url: string,
  params?: Record<string, any>,
  config?: CustomRequestConfig
): Promise<ApiResponse<T>> => {
  return request.get(url, { params, ...config });
};

export const post = <T = any>(
  url: string,
  data?: Record<string, any> | FormData, // 支持FormData类型
  config?: CustomRequestConfig
): Promise<ApiResponse<T>> => {
  return request.post(url, data, config);
};

export default request;

5.2 配置环境变量(项目根目录)

创建 .env.development(开发环境)和 .env.production(生产环境):

ini 复制代码
# .env.development(本地开发)
VITE_API_BASE_URL = 'http://localhost:3000/api'

# .env.production(生产环境)
VITE_API_BASE_URL = 'https://api.yourdomain.com'

5.3 组件使用(文件上传示例)

xml 复制代码
<script setup lang="ts">
import { post } from '@/utils/request';

// 文件上传:自动适配multipart/form-data请求头
const uploadFile = async (file: File) => {
  const formData = new FormData();
  formData.append('file', file);
  
  const res = await post<{ url: string }>('/file/upload', formData);
  console.log('文件地址:', res.data.url);
};
</script>

5.4 关键解释

  • 环境变量:通过 import.meta.env.VITE_API_BASE_URL 读取不同环境的接口地址,避免打包时修改代码;
  • Content-Type 自动适配:检测到 FormData 时,自动将请求头改为 multipart/form-data,无需手动配置;
  • 生产环境隔离:开发 / 生产环境的接口地址通过环境变量区分,符合工程化最佳实践。

总结

从简到繁的封装核心步骤可归纳为:

  1. 基础层:创建 axios 实例 + 固定默认请求头,满足最基本的请求头配置;
  2. 核心层:添加请求拦截器,实现 Token 等动态请求头的按需添加;
  3. 类型层 :完善 TypeScript 类型约束,避免 any 类型,保证类型安全;
  4. 易用层 :封装 get/post 通用方法,简化组件调用,提升开发效率;
  5. 优化层:自动适配 Content-Type、配置环境变量,适配复杂业务场景。
相关推荐
学海无涯,行者无疆11 天前
前端 Axios 深度封装实战:拦截器 + 文件处理 + 业务接口统一管理
axios·前后端交互·axios使用·axios实战·axios封装·axios详解·axios用法
Java程序员-小白24 天前
Sa-Token过滤器引发的CORS误判问题
vue.js·elementui·axios·cors
stella·25 天前
后端二进制文件,现代前端如何下载
前端·ajax·状态模式·axios·request·buffer·download
Irene19911 个月前
fetch 和 axios 对比总结
axios·fetch
孜孜不倦不忘初心1 个月前
Axios 常用配置及使用
前端·axios
搬砖的阿wei1 个月前
JavaScript 请求数据的四种方法:Ajax、jQuery 、Fetch和 Axios
javascript·ajax·axios·jquery
鹏北海2 个月前
Vue3 + Axios 企业级请求封装实战:从零搭建完整的 HTTP 请求层
前端·vue.js·axios
辛-夷2 个月前
TS封装axios
前端·vue.js·typescript·vue·axios
跟着珅聪学java2 个月前
Axios HTTP请求超时时间参数配置教程
axios