首页
/ axios TypeScript 类型实战指南:泛型请求标注、类型化实例、拦截器与错误收窄

axios TypeScript 类型实战指南:泛型请求标注、类型化实例、拦截器与错误收窄

2026-09-06 13:48:34作者:秋泉律Samson

axios 在仓库根目录的 index.d.ts 中内置了完整的 TypeScript 类型定义,开发者无需额外安装任何 @types 包。本文围绕官方文档《TypeScript 示例》展开,逐一讲解如何为 GET/POST 请求、封装函数、axios 实例、拦截器和错误处理标注类型,并结合 index.d.tslib/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" 导入所需的类型,例如 AxiosRequestConfigAxiosResponseAxiosError

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)。这意味着如果你使用实例的 defaultsAxiosInstance.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-L578AxiosResponseResult 计算:当第二个泛型 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-L47createInstance:它 new Axios(defaultConfig) 后,把 Axios.prototype 上的方法(getpost 等)与实例属性合并绑定到同一个函数对象上,并挂载 instance.create 用于派生新实例。而 lib/core/Axios.js#L22-L29 的构造函数会初始化 this.defaultsthis.interceptors(分别持有 request / response 两个 InterceptorManager),这正是下文拦截器挂载点 api.interceptors 的来源。

需要注意:默认泛型下 AxiosInstancedataparams 仍为 any。若想让整个实例的所有请求共享同一套数据/参数类型,可以通过 AxiosRequestConfig<D, P>D/P 泛型在声明 defaults 或调用 create 时显式指定(见 index.d.ts#L504-L513AxiosDefaults<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 对象,因此 InternalAxiosRequestConfigheaders 收紧为必填的 AxiosRequestHeaders。这就是为什么拦截器里可以直接调用 config.headers.set(...) 而不需要做空值判断。

AxiosHeaders 是一个类(index.d.ts#L25-L103),除了 set/get/has/delete 等通用方法,还为常用请求头生成了类型化访问器:setAuthorization / getAuthorizationsetContentTypesetAcceptsetUserAgent 等,类型层面即可拼错头名。interceptors 的类型由 index.d.ts#L649-L652 定义:requestAxiosInterceptorManager<InternalAxiosRequestConfig>responseAxiosInterceptorManager<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_NETWORKECONNABORTEDETIMEDOUTERR_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"

这三条注意事项与仓库的实际结构一一对应:

  1. 双入口声明文件package.json#L12-L34exports 中,types.require 指向 index.d.ctstypes.default 指向 index.d.ts。只有 node16 / nodenext 解析策略会读取 exports 条件并区分 CJS 与 ESM 声明文件;旧版 node 解析只会看顶层 types 字段。
  2. 仓库自身即采用该配置:根目录 tsconfig.json 设置了 "module": "node16""strict": true,可直接作为参考基线。
  3. 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),即可在严格模式下获得端到端可推导的请求类型体验。

登录后查看全文
热门项目推荐
相关项目推荐