axios 请求头封装过程遵循「最小可用 → 逐步增强」
步骤 1:极简版 - 基础实例 + 默认请求头
这是最基础的封装,仅创建 axios 实例并配置固定默认请求头,满足最基本的请求头需求,适合新手入门。
1.1 安装依赖
npm install axios
# 或 yarn add axios
1.2 基础封装代码(src/utils/request.ts)
// 第一步:仅创建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 组件中简单使用
<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)
// 第二步:新增请求拦截器,动态添加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)
<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类型约束
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 组件使用(带类型提示)
<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)
// 第四步:封装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 组件使用(更简洁)
<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)
// 第五步:进阶优化(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(生产环境):
# .env.development(本地开发)
VITE_API_BASE_URL = 'http://localhost:3000/api'
# .env.production(生产环境)
VITE_API_BASE_URL = 'https://api.yourdomain.com'
5.3 组件使用(文件上传示例)
<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,无需手动配置; - 生产环境隔离:开发 / 生产环境的接口地址通过环境变量区分,符合工程化最佳实践。
总结
从简到繁的封装核心步骤可归纳为:
- 基础层:创建 axios 实例 + 固定默认请求头,满足最基本的请求头配置;
- 核心层:添加请求拦截器,实现 Token 等动态请求头的按需添加;
-
类型层:完善 TypeScript 类型约束,避免
any类型,保证类型安全; -
易用层:封装
get/post通用方法,简化组件调用,提升开发效率; - 优化层:自动适配 Content-Type、配置环境变量,适配复杂业务场景。