axios TypeScript 类型实战指南:泛型请求标注、类型化实例、拦截器与错误收窄
axios 在仓库根目录的 index.d.ts 中内置了完整的 TypeScript 类型定义,开发者无需额外安装任何 @types 包。本文围绕官方文档《TypeScript 示例》展开,逐一讲解如何为 GET/POST 请求、封装函数、axios 实例、拦截器和错误处理标注类型,并结合 index.d.ts、lib/axios.js 等源码说明这些类型的实际结构。读完本文,你可以掌握在严格模式下使用 axios 的完整类型方案,并知道如何配置 tsconfig 才能正确解析 axios 的双模块类型。
内置类型定义与导入方式
axios 同时发布了 ESM 和 CJS 版本,并在 package.json 中声明了类型入口:"types": "index.d.ts"、"typings": "./index.d.ts",并且 exports 字段针对 require 条件单独指向了 index.d.cts(CJS 专用声明文件),针对默认导入指向 index.d.ts(ESM 声明文件)。index.d.ts 第一行标注 // TypeScript Version: 4.7,即类型定义要求最低 TypeScript 4.7。
你可以直接从 "axios" 导入所需的类型,例如 AxiosRequestConfig、AxiosResponse、AxiosError:
import axios from "axios";
import type { AxiosRequestConfig, AxiosResponse, AxiosError } from "axios";
这三个类型在 index.d.ts 中的定义带有两个泛型参数 <D = any, P = any>:D 对应请求体(data 字段,见 index.d.ts#L403),P 对应查询参数(params 字段,见 index.d.ts#L399)。这意味着如果你使用实例的 defaults 或 AxiosInstance.create(),也可以让请求头和默认配置继承特定的数据/参数类型。
为 GET 请求标注类型
在响应上使用泛型参数,告知 TypeScript 数据的具体结构。以 jsonplaceholder 的文章接口为例:
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 知道这是一个字符串
get 方法的完整签名在 index.d.ts#L657-L660:
get<T = any, R = AxiosResponseDefault, D = any, P = any>(
url: string,
config?: AxiosRequestConfig<D, P>
): Promise<AxiosResponseResult<T, R, D, P>>;
其中第一个泛型 T 就是 response.data 的类型。返回类型经由 index.d.ts#L572-L578 的 AxiosResponseResult 计算:当第二个泛型 R 取默认值 AxiosResponseDefault(一个 unique symbol)时,结果就是 AxiosResponse<T, D, {}, P>。这个设计还带来一个进阶用法——你可以把 R 显式传为自定义结构,从而彻底替换默认的 AxiosResponse 响应包装(适合响应经过 transformResponse 解包后只剩 data 的场景)。
默认情况下 AxiosResponse 的结构由 index.d.ts#L515-L522 定义,可访问的字段为:
| 字段 | 类型 | 说明 |
|---|---|---|
data |
T(由泛型指定,默认 any) |
服务端返回的响应体 |
status |
number |
HTTP 状态码 |
statusText |
string |
状态文本 |
headers |
RawAxiosResponseHeaders | AxiosResponseHeaders |
响应头,含 set-cookie: string[] 等常见响应头键 |
config |
InternalAxiosRequestConfig<D, P> |
最终合并后的请求配置 |
request |
any(可选) |
发起请求的底层对象 |
为函数标注类型
将请求封装在函数中,并明确声明返回类型,以获得最佳的类型安全性:
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;
};
由于 axios.get<Post>(...) 的返回类型是 Promise<AxiosResponse<Post, any, {}, any>>,response.data 的类型就是 Post,因此函数签名声明为 Promise<Post> 可以直接通过类型检查。把泛型约束在"发起请求处"、把返回类型约束在"封装函数签名处",两者结合后,调用方完全不需要关心 AxiosResponse 包装层。
为 POST 请求标注类型
你可以同时为请求体和预期响应标注类型:
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;
};
post 的签名为 index.d.ts#L673-L677:
post<T = any, R = AxiosResponseDefault, D = any, P = any>(
url: string,
data?: D,
config?: AxiosRequestConfig<D, P>
): Promise<AxiosResponseResult<T, R, D, P>>;
这里第一个泛型 T 仍然描述响应体 response.data,而请求体 data 的类型由第三个泛型 D 承载,并同步约束 config 中的 data?: D 字段(index.d.ts#L403)。因此如果你希望请求体也参与类型推导,可以显式传入 axios.post<CreatePostResponse, typeof axiosResponseDefault, CreatePostBody> 或在配置对象上标注类型;上例中最简单可靠的做法是依赖函数参数 data: CreatePostBody 做显式约束。响应类型用 CreatePostBody & { id: number } 这种交叉类型组合,可以避免重复声明已有字段。
带类型的 axios 实例
创建一个带类型的实例,将 baseURL 和请求头内置其中:
import axios from "axios";
import type { AxiosInstance } from "axios";
const api: AxiosInstance = axios.create({
baseURL: "https://api.example.com",
timeout: 5000,
});
AxiosInstance 定义在 index.d.ts#L710-L725,它继承自 Axios 类接口并额外提供两种可调用形式:api(config) 与 api(url, config?),以及 create(config?) 工厂方法和按方法划分的 defaults.headers。也就是说,标注为 AxiosInstance 的变量既可以 api.get(...) 也可以用 api({ url, method }) 直接调用。
从源码结构看,axios.create 最终调用 lib/axios.js#L28-L47 的 createInstance:它 new Axios(defaultConfig) 后,把 Axios.prototype 上的方法(get、post 等)与实例属性合并绑定到同一个函数对象上,并挂载 instance.create 用于派生新实例。而 lib/core/Axios.js#L22-L29 的构造函数会初始化 this.defaults 和 this.interceptors(分别持有 request / response 两个 InterceptorManager),这正是下文拦截器挂载点 api.interceptors 的来源。
需要注意:默认泛型下 AxiosInstance 的 data 与 params 仍为 any。若想让整个实例的所有请求共享同一套数据/参数类型,可以通过 AxiosRequestConfig<D, P> 的 D/P 泛型在声明 defaults 或调用 create 时显式指定(见 index.d.ts#L504-L513 的 AxiosDefaults<D, P> 与 CreateAxiosDefaults<D, P>)。
带类型的拦截器
在 v1.x 中,请求拦截器应使用 InternalAxiosRequestConfig(而非 AxiosRequestConfig):
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)
);
两者差异在 index.d.ts#L485-L487 中一目了然:
export interface InternalAxiosRequestConfig<D = any, P = any> extends AxiosRequestConfig<D, P> {
headers: AxiosRequestHeaders;
}
AxiosRequestConfig.headers 是可选的、类型较宽(RawAxiosRequestHeaders & MethodsHeaders | AxiosHeaders,见 index.d.ts#L398);而进入请求拦截器阶段时,axios 已将请求头解析并实例化为 AxiosHeaders 对象,因此 InternalAxiosRequestConfig 把 headers 收紧为必填的 AxiosRequestHeaders。这就是为什么拦截器里可以直接调用 config.headers.set(...) 而不需要做空值判断。
AxiosHeaders 是一个类(index.d.ts#L25-L103),除了 set/get/has/delete 等通用方法,还为常用请求头生成了类型化访问器:setAuthorization / getAuthorization、setContentType、setAccept、setUserAgent 等,类型层面即可拼错头名。interceptors 的类型由 index.d.ts#L649-L652 定义:request 是 AxiosInterceptorManager<InternalAxiosRequestConfig>,response 是 AxiosInterceptorManager<AxiosResponse>,use 的返回值是拦截器 id(可用于 eject 移除,见 index.d.ts#L639-L644)。
为错误标注类型
使用 axios.isAxiosError() 对捕获的错误进行类型收窄:
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 的类型为 ApiError
console.error(error.response?.data.message);
console.error(error.response?.status);
} else {
throw error;
}
}
isAxiosError 的类型签名是一个类型守卫(index.d.ts#L749-L751):
export function isAxiosError<T = any, D = any, P = any>(
payload: any
): payload is AxiosError<T, D, P>;
传入 ApiError 后,分支内 error 被收窄为 AxiosError<ApiError>,其 response?: AxiosResponse<ApiError, ...>(index.d.ts#L536),所以 error.response?.data.message 可安全访问。运行时的判断实现非常直接,见 lib/helpers/isAxiosError.js#L12-L14:判断 payload 是否为对象且 payload.isAxiosError === true。这个标志位在 lib/core/AxiosError.js#L144-L169 的构造函数中被置为 true,同时构造器会在存在 response 时把 this.status 同步为 response.status——这正是示例中 error.response?.status 可用的原因。
AxiosError 的类型声明(index.d.ts#L524-L564)还包含:
code?: string:错误码,静态常量如ERR_NETWORK、ECONNABORTED、ETIMEDOUT、ERR_CANCELED等(index.d.ts#L550-L563);config?: InternalAxiosRequestConfig<D, P>与request?: any:出错时保留的请求现场;cause?: Error:包装底层错误的来源,与原生Error.cause语义一致。
TypeScript 配置说明
由于 axios 同时发布了 ESM 和 CJS 版本,根据你的配置不同,可能存在以下注意事项:
- 推荐设置为
"moduleResolution": "node16"(由"module": "node16"隐式指定),需要 TypeScript 4.7 或更高版本。 - 如果你将 TypeScript 编译为 CJS 且无法使用
"moduleResolution": "node16",请启用"esModuleInterop": true。 - 如果你使用 TypeScript 对 CJS JavaScript 代码进行类型检查,则只能使用
"moduleResolution": "node16"。
这三条注意事项与仓库的实际结构一一对应:
- 双入口声明文件:package.json#L12-L34 的
exports中,types.require指向 index.d.cts、types.default指向 index.d.ts。只有node16/nodenext解析策略会读取exports条件并区分 CJS 与 ESM 声明文件;旧版node解析只会看顶层types字段。 - 仓库自身即采用该配置:根目录 tsconfig.json 设置了
"module": "node16"与"strict": true,可直接作为参考基线。 - CI 中的类型回归测试:tests/module/esm/tests/typings.module.test.js 会临时创建 fixture,用
module: "node16"的 tsconfig 运行tsc --noEmit验证 ESM 声明文件;CJS 侧由 tests/module/cjs 下的同名测试覆盖。可以推断,这两组测试就是保证上述配置建议在版本迭代中持续成立的自动化手段。
小结
axios 的 TypeScript 支持围绕 index.d.ts 中一组核心类型展开:用 T 泛型标注 response.data、用 D/P 泛型约束请求体与参数、用 InternalAxiosRequestConfig 收紧拦截器内的请求头、用 isAxiosError<T> 守卫收窄错误类型、用 AxiosInstance 承载可调用实例。配合 "module": "node16" 的解析配置(或 CJS 下的 esModuleInterop),即可在严格模式下获得端到端可推导的请求类型体验。
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 StartedRust0625
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