axios TypeScript 完全实战指南:类型导入、泛型请求、有类型实例、拦截器与错误守卫
axios 在 v1.x 中随 npm 包内置完整的 TypeScript 类型定义,开发者无需额外安装 @types 依赖,即可获得从请求、响应、错误到实例与拦截器全链路的类型安全。本篇以官方 Getting Started 中的 TypeScript 示例文档 为主线,完整覆盖类型导入、泛型请求、函数封装、POST 类型标注、有类型实例、有类型拦截器、错误类型收窄等实战写法,并结合 index.d.ts 源码级定义与模块类型测试,说明每个类型背后的实际契约与模块配置注意事项,帮助你在浏览器与 Node.js 项目中写出零类型报错的 axios 客户端代码。
axios 随包提供双格式类型定义
axios 采用 ESM/CJS 双发布策略,类型文件也随之提供两份入口。在 package.json 的 exports 字段中可以看到明确的映射关系:
{
"types": {
"require": "./index.d.cts",
"default": "./index.d.ts"
}
}
也就是说,当 TypeScript 以 ESM 方式解析时加载 index.d.ts,以 CommonJS require 方式解析时加载 index.d.cts。这正是后文模块配置注意事项的根源。
类型文件的顶层导出结构可以在 index.d.ts 中确认:默认导出 axios 的类型是 AxiosStatic,它继承自 AxiosInstance,并挂载了 Axios 类、AxiosError、isAxiosError、isCancel、toFormData、mergeConfig 等全部静态成员:
export interface AxiosStatic extends AxiosInstance {
Cancel: typeof CanceledError;
CancelToken: CancelTokenStatic;
Axios: typeof Axios;
AxiosError: typeof AxiosError;
HttpStatusCode: typeof HttpStatusCode;
readonly VERSION: string;
isCancel: typeof isCancel;
// ...all, spread, isAxiosError, toFormData, formToJSON, getAdapter, mergeConfig
}
declare const axios: AxiosStatic;
export default axios;
这意味着 import axios from "axios" 得到的默认导出天然带有完整的请求方法与静态工具函数签名。
导入类型
axios 的公共类型均从 "axios" 直接导出,无需深入内部路径:
import axios from "axios";
import type { AxiosRequestConfig, AxiosResponse, AxiosError } from "axios";
三个最常用的类型在 index.d.ts 中的定义如下,理解它们的泛型参数是后面所有写法的基础:
AxiosRequestConfig<D = any, P = any>(L391):D表示请求体数据类型,P表示 query 参数类型,包含url、baseURL、timeout、headers、params、validateStatus、signal等全部请求配置字段;AxiosResponse<T = any, D = any, H = {}, P = any>(L515):data: T、status、statusText、headers,并且config字段类型是InternalAxiosRequestConfig<D, P>;AxiosError<T = unknown, D = any, P = any> extends Error(L524):带有config、code、response?: AxiosResponse<T, D, {}, P>、isAxiosError: boolean属性,并声明了ERR_NETWORK、ETIMEDOUT、ECONNABORTED等静态错误码常量。
另外,AxiosRequestConfig 在 L483 有一个别名 RawAxiosRequestConfig,二者等价,可互换使用。
为 GET 请求标注响应类型
通过响应泛型参数告诉 TypeScript 数据的具体形状。axios.get<T> 的返回是 Promise<AxiosResponseResult<T, R, D, P>>,当未提供自定义响应类型 R 时,结果就是标准的 AxiosResponse<T>(见 index.d.ts 的 AxiosResponseResult 定义),因此 response.data 会被精确标注为你传入的类型:
import axios from "axios";
type Post = {
userId: number;
id: number;
title: string;
body: string;
};
const response = await axios.get<Post>("https://jsonplaceholder.typicode.com/posts/1");
console.log(response.data.title); // TypeScript knows this is a string
由于 Post.title 被声明为 string,如果误写 response.data.title.length 之外的属性(例如 response.data.content)会立即在编译期报错,而不是拖到运行时。
将请求封装为有类型的函数
把请求包裹在显式返回类型的函数里,可以在调用链的更上游就获得类型保障:
import axios, { AxiosResponse } from "axios";
type Post = {
userId: number;
id: number;
title: string;
body: string;
};
const getPost = async (id: number): Promise<Post> => {
const response = await axios.get<Post>(
`https://jsonplaceholder.typicode.com/posts/${id}`
);
return response.data;
};
这里 await axios.get<Post>(...) 得到 AxiosResponse<Post>,解构出的 response.data 正是 Post,与函数声明的 Promise<Post> 返回值一致。把 data 而非整个 AxiosResponse 暴露给业务层,可以让上层代码只关心数据形状。
为 POST 请求同时标注请求体与响应
请求方法除响应类型 T 外,还支持第三个泛型位置描述请求体类型 D。实践中最常见的是先定义请求体与响应两个类型,再用泛型约束响应:
type CreatePostBody = {
title: string;
body: string;
userId: number;
};
type CreatePostResponse = CreatePostBody & { id: number };
const createPost = async (data: CreatePostBody): Promise<CreatePostResponse> => {
const response = await axios.post<CreatePostResponse>(
"https://jsonplaceholder.typicode.com/posts",
data
);
return response.data;
};
如果希望显式利用 D 位置(请求体类型),可以把请求方法签名的 <T, R, D, P> 四个泛型都用上——axios.post<CreatePostResponse, undefined, CreatePostBody>(...),此时 response.config.data 也会被推断为 CreatePostBody | undefined。完整的请求数据与 query 参数双泛型用法(AxiosRequestConfig<D, P>、paramsSerializer 的类型推断)可参阅进阶文档 docs/pages/advanced/type-script.md。
创建有类型的 axios 实例
用 AxiosInstance 标注 axios.create 的返回值,把 baseURL、timeout、默认请求头等“烘焙”进客户端,后续所有请求方法都继承该类型:
import axios from "axios";
import type { AxiosInstance } from "axios";
const api: AxiosInstance = axios.create({
baseURL: "https://api.example.com",
timeout: 5000,
});
从源码看(index.d.ts),AxiosInstance 继承自 Axios 接口,并额外提供:
- 两个可调用签名:
api(config)与api(url, config),分别对应以完整配置或 URL+配置发起请求; create(config?: CreateAxiosDefaults): AxiosInstance,支持从已有实例再派生子实例;defaults属性,类型是Omit<AxiosDefaults, 'headers'>加上按方法分组的headers。
CreateAxiosDefaults(L508-L513)与 AxiosRequestConfig 的区别在于 headers 的写法更宽松,允许传入 RawAxiosRequestHeaders | AxiosHeaders | Partial<HeadersDefaults>,因此实例级默认头可以写成 { common: {...}, post: {...} } 的形式。
请求拦截器:必须使用 InternalAxiosRequestConfig
v1.x 中请求拦截器必须使用 InternalAxiosRequestConfig(而不是 AxiosRequestConfig)。原因在于二者的 headers 字段类型不同(index.d.ts):
export interface InternalAxiosRequestConfig<D = any, P = any> extends AxiosRequestConfig<D, P> {
headers: AxiosRequestHeaders; // 必填,且是 AxiosHeaders 实例
}
在 AxiosRequestConfig 中 headers 是可选的,且允许普通对象或 AxiosHeaders;而请求经过默认合并后,headers 一定已经是一个 AxiosHeaders 实例(提供 .set()、.get() 等方法)。拦截器拿到的是合并后的内部配置,所以类型必须与运行时形态一致:
import axios from "axios";
import type { InternalAxiosRequestConfig, AxiosResponse } from "axios";
api.interceptors.request.use((config: InternalAxiosRequestConfig) => {
config.headers.set("Authorization", `Bearer ${getToken()}`);
return config;
});
api.interceptors.response.use(
(response: AxiosResponse) => response,
(error) => Promise.reject(error)
);
注意 config.headers.set(...) 这种调用方式正是 headers 被标注为 AxiosRequestHeaders(AxiosHeaders 实例类型)的直接体现;如果误用 AxiosRequestConfig,在开启严格检查时 set 方法不一定可用。
错误类型收窄:isAxiosError 类型守卫
catch 块中 error 默认是 unknown。使用 axios.isAxiosError<T>() 可以安全地把错误收窄为 AxiosError<T>,从而在编译期安全访问 error.response?.data、error.response?.status:
import axios, { AxiosError } from "axios";
type ApiError = {
message: string;
code: number;
};
try {
await axios.get("/api/protected-resource");
} catch (error) {
if (axios.isAxiosError<ApiError>(error)) {
// error.response?.data is typed as ApiError
console.error(error.response?.data.message);
console.error(error.response?.status);
} else {
throw error;
}
}
类型层面,该守卫在 index.d.ts 中声明为类型谓词:
export function isAxiosError<T = any, D = any, P = any>(
payload: any
): payload is AxiosError<T, D, P>;
运行时实现极为简洁,见 lib/helpers/isAxiosError.js:
export default function isAxiosError(payload) {
return utils.isObject(payload) && payload.isAxiosError === true;
}
即只要对象上带有 isAxiosError === true 标记(该属性在 AxiosError 构造时写入)就判定为 axios 错误——这也解释了为什么跨 bundle 边界(如错误从 Worker 抛出)后类型收窄依然有效。类似的守卫还有 axios.isCancel<T>()(L755),用于把取消错误收窄为 CanceledError<T>;而 AxiosError 上声明的 ERR_NETWORK、ETIMEDOUT、ECONNABORTED 等静态错误码常量(L550-L563)则可用于按 error.code 做分支处理。
TypeScript 配置注意事项
由于 axios 同时发布 ESM(module 字段指向 ./index.js,默认导出)和 CJS(main 指向 ./dist/node/axios.cjs,module.exports),不同 tsconfig 下需要满足不同的前提:
| 场景 | 配置要求 |
|---|---|
| 推荐 | "moduleResolution": "node16"(由 "module": "node16" 隐含),需要 TypeScript 4.7+ |
编译到 CJS 且无法使用 node16 |
必须开启 "esModuleInterop": true,否则默认导出互操作会报错 |
| 用 TypeScript 检查 CJS 的 JavaScript 代码 | 唯一选择是 "moduleResolution": "node16" |
仓库自身的模块类型测试印证了这些约束:
- ESM 侧的 tests/module/esm/tests/ts.module.test.js 在运行时构造了
module: "node16"的编译选项做类型检查; - CJS 侧的 tests/module/cjs/tests/ts-require-default.module.test.cjs 则使用
moduleResolution: "node"搭配esModuleInterop: true来验证require("axios")的默认导入。
也就是说,如果你的老项目还在 "moduleResolution": "node"(Node10 解析)下以 CJS 方式 require axios,补上 "esModuleInterop": true 即可正常获得类型支持;而新版 TypeScript 项目直接采用 node16 解析是最省事、也最能正确匹配上面 exports 映射(含 .d.cts 与 .d.ts 分流)的方案。
常用类型速查
| 类型 | 定义位置 | 用途 |
|---|---|---|
AxiosRequestConfig<D, P> |
index.d.ts#L391 | 描述请求配置,D 为请求体类型、P 为 query 参数类型 |
RawAxiosRequestConfig |
index.d.ts#L483 | AxiosRequestConfig 的别名 |
InternalAxiosRequestConfig<D, P> |
index.d.ts#L485 | 请求拦截器专用,headers 必为 AxiosRequestHeaders |
AxiosResponse<T, D, H, P> |
index.d.ts#L515 | 响应对象,data: T、config: InternalAxiosRequestConfig<D, P> |
AxiosError<T, D, P> |
index.d.ts#L524 | 错误类,含 response?、code? 与静态错误码 |
AxiosPromise<T, D, P> |
index.d.ts#L580 | Promise<AxiosResponse<T, D, {}, P>> 的别名 |
AxiosInstance |
index.d.ts#L710 | 可调用实例,axios.create 的返回类型 |
AxiosStatic |
index.d.ts#L766 | 默认导出 axios 的类型 |
isAxiosError<T, D, P> |
index.d.ts#L749 | 错误收窄的类型守卫 |
isCancel<T, D, P> |
index.d.ts#L755 | 取消错误收窄的类型守卫 |
掌握以上类型后,从单条 axios.get<Post>(...) 到带 AxiosInstance、拦截器与错误守卫的完整客户端,都可以在编译期完成验证。若需要更深入的用法——如请求数据与 query 参数的双泛型推断、paramsSerializer 的类型、Symbol 键自定义配置的模块增强(declare module "axios")——可继续参阅 TypeScript 进阶文档 以及 CommonJS 场景的 CommonJS 示例文档。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00