首页
/ Axios TypeScript 类型系统详解:模块解析、错误 Type Guard 与泛型化请求配置(axios)

Axios TypeScript 类型系统详解:模块解析、错误 Type Guard 与泛型化请求配置(axios)

2026-09-06 12:47:38作者:盛欣凯Ernestine

本文为 axios(Promise based HTTP client for the browser and node.js)TypeScript 支持的技术指南,围绕其官方文档 docs/fr/pages/advanced/type-script.md 的核心内容展开。读完本篇,你将掌握:axios 在 ESM/CJS 双模块格式下的 TypeScript 解析配置要点、如何用 isAxiosError / isCancelcatch 中的 unknown 错误做类型收窄,以及如何通过 AxiosRequestConfig<D, P> 双泛型、可调用 AxiosInstance、Symbol 键模块增强等机制实现请求/响应数据的端到端类型安全。

类型定义文件的发布方式

axios 通过 npm 包内置 TypeScript 类型声明:ESM 入口对应 index.d.ts,CommonJS 入口对应 index.d.cts,因此无论使用哪种模块格式,类型检查与编辑器补全开箱即用。这一点可以直接在 package.json 中得到验证:

  • 顶层字段 "types": "index.d.ts""typings": "./index.d.ts" 声明了默认类型入口;
  • exports 字段中通过 "require": "./index.d.cts""default": "./index.d.ts"types 条件,把类型声明按模块解析方式精确路由到对应文件;
  • 运行时入口同样分离:require 指向 ./dist/node/axios.cjs,默认指向 ESM 的 ./index.js

正是这种「ESM 默认导出 + CJS module.exports」的双发布形态,引出了下面这组配置注意事项。

模块解析的配置要点

由于 axios 同时发布 ESM 默认导出和 CJS 的 module.exports,TypeScript 项目需要留意以下解析细节(以下配置要求以文档为准,推荐 TypeScript 4.7+):

你的场景 建议配置
推荐方案 "moduleResolution": "node16"(由 "module": "node16" 隐含启用),要求 TypeScript ≥ 4.7
项目本身使用 ESM 通常无需额外调整,现有配置即可正确解析
编译到 CJS 且无法使用 node16 解析 必须开启 esModuleInterop,否则默认导入的互操作类型会报错
用 TypeScript 检查 CJS 格式的 JavaScript 代码 唯一可行的选项是 "moduleResolution": "node16"

其原理对应 exports 映射:node16 解析器会依据你的文件是 ESM 还是 CJS 来选择 index.d.ts / index.d.cts,并正确处理默认导出互操作;而旧的 node(node10)解析器只能看到 types 顶层字段,无法感知条件导出,所以才需要 esModuleInterop 兜底。

错误类型守卫:isAxiosError 与 isCancel

catch 块中,error 的静态类型是 unknown。axios 提供两个类型守卫函数安全收窄类型,其声明位于 index.d.ts

export function isAxiosError<T = any, D = any, P = any>(
  payload: any
): payload is AxiosError<T, D, P>;

export function isCancel<T = any, D = any, P = any>(value: any): value is CanceledError<T, D, P>;

使用 axios.isAxiosError 收窄之后,即可在完全类型安全的状态下访问 error.responseerror.configerror.code 等 axios 专属属性:

import axios from "axios";

let user: User | null = null;
try {
  const { data } = await axios.get("/user?ID=12345");
  user = data.userDetails;
} catch (error) {
  if (axios.isAxiosError(error)) {
    handleAxiosError(error);
  } else {
    handleUnexpectedError(error);
  }
}

运行时实现非常简单且可靠,见 lib/helpers/isAxiosError.js

export default function isAxiosError(payload) {
  return utils.isObject(payload) && payload.isAxiosError === true;
}

它检查对象上是否存在 isAxiosError === true 标记位,而该标记正对应 index.d.tsAxiosError 类的 isAxiosError: boolean 属性——类型系统与运行时行为严格一致。

对于请求取消(例如使用 AbortControllersignal 场景),用 axios.isCancel<T>() 将错误收窄为 CanceledError<T>

const controller = new AbortController();

try {
  await axios.get<User>("/user?ID=12345", { signal: controller.signal });
} catch (error) {
  if (axios.isCancel<User>(error)) {
    handleCancellation(error);
  }
}

从类型声明看(index.d.ts),CanceledError<T, D, P> 继承自 AxiosError<T, D, P>,带有 name: 'CanceledError' 与可选的 __CANCEL__ 标记,因此收窄后可以访问与 AxiosError 相同的全部属性。AxiosError 上还以静态常量形式列出了常见的错误码(ERR_NETWORKETIMEDOUTECONNABORTED 等,见 index.d.ts),可用于 error.code 的枚举式比对。

请求数据与查询参数的双泛型:D 与 P

AxiosRequestConfig<D = any, P = any> 的两个泛型参数分别承载请求体数据D)与查询参数P),自定义的参数序列化器也能拿到同样的 P。该接口定义在 index.d.ts

export interface AxiosRequestConfig<D = any, P = any> {
  // ...省略其余配置项...
  params?: P;
  paramsSerializer?:
    | ParamsSerializerOptions<unknown extends P ? Record<string, any> : P>
    | CustomParamsSerializer<unknown extends P ? Record<string, any> : P>;
  data?: D;
  // ...
}

注意 paramsSerializer 的条件类型 unknown extends P ? Record<string, any> : P:当 P 被显式指定为具体类型时,序列化器参数获得精确类型;当 P 为默认的 any 时回退为 Record<string, any>,从而保证向后兼容。完整示例:

import axios, {
  type AxiosPromise,
  type AxiosRequestConfig,
  type InternalAxiosRequestConfig,
} from "axios";

interface RequestBody {
  includeArchived: boolean;
}

interface SearchParams {
  query: string;
  page?: number;
}

interface SearchResponse {
  results: string[];
}

const searchConfig: AxiosRequestConfig<RequestBody, SearchParams> = {
  data: { includeArchived: false },
  params: { query: "axios", page: 1 },
  paramsSerializer: (params) => `${params.query}:${params.page ?? 1}`,
};

const response = await axios.get("/search", searchConfig);
response.config.data;   // RequestBody | undefined
response.config.params; // SearchParams | undefined

const invalidConfig: AxiosRequestConfig<RequestBody, SearchParams> = {
  // @ts-expect-error `query` 必须是字符串
  params: { query: 123 },
};

这段代码演示了两个关键能力:

  1. 响应回传:默认响应中 response.config 保留了 DPAxiosResponseconfig 字段类型即 InternalAxiosRequestConfig<D, P>,见 index.d.ts),即使请求是通过带类型的配置推断出来的别名方法,DP 依然被完整携带;
  2. 编译期拦截错误params 的类型是 SearchParams,把 query 写成数字会在编译期被 @ts-expect-error 捕获。

AxiosResponseAxiosPromise 外,RawAxiosRequestConfigInternalAxiosRequestConfigAxiosDefaultsCreateAxiosDefaultsAxiosErrorCanceledError、可调用实例、适配器等类型,以及 mergeConfig() 函数(index.d.tsmergeConfig<D, P>(config1, config2): AxiosRequestConfig<D, P>)也都会保留 P,即参数类型不会在整条配置链路上丢失。

请求方法新增的第四个泛型 P

请求方法(get/post/put 等)在原有三个泛型之后追加了 P,签名为 <T, R, D, P>index.d.ts):

get<T = any, R = AxiosResponseDefault, D = any, P = any>(
  url: string,
  config?: AxiosRequestConfig<D, P>
): Promise<AxiosResponseResult<T, R, D, P>>;
  • T:响应数据类型;
  • R:自定义响应类型(默认哨兵值 AxiosResponseDefault 表示使用标准 AxiosResponse,见 index.d.tsAxiosResponseResult 条件类型);
  • D:请求数据;
  • P:查询参数,默认 any 以保持向后兼容。

由于 P 追加在末尾,既有代码中 TRD 的位置不变,现有泛型写法无需迁移。显式提供的响应类型 R 仍然决定 Promise 的 resolve 值。

显式带类型的适配器(或任意 Promise)同样可以保留两个请求类型:

const searchAdapter = (
  config: InternalAxiosRequestConfig<RequestBody, SearchParams>
): AxiosPromise<SearchResponse, RequestBody, SearchParams> =>
  Promise.resolve({
    data: { results: [] },
    status: 200,
    statusText: "OK",
    headers: {},
    config,
  });

declare const error: unknown;

if (axios.isCancel<SearchResponse, RequestBody, SearchParams>(error)) {
  error.config?.data;   // RequestBody | undefined
  error.config?.params; // SearchParams | undefined
}

这里 AxiosPromise<T, D, P> 被声明为 Promise<AxiosResponse<T, D, {}, P>>index.d.ts),错误守卫 isCancel<T, D, P> 收窄后也能读取带类型的 config.data / config.params,形成「请求 → 响应 → 错误」三个方向一致的类型闭环。

类型化的实例与拦截器

AxiosInstance 标注 axios.create 的返回值,并用 InternalAxiosRequestConfig 标注请求拦截器参数,即可为自定义客户端获得端到端的类型检查:

import axios, { AxiosInstance, InternalAxiosRequestConfig } from "axios";

const apiClient: AxiosInstance = axios.create({
  baseURL: "https://api.example.com",
  timeout: 10000,
});

apiClient.interceptors.request.use((config: InternalAxiosRequestConfig) => {
  // 添加认证 token、记录日志等
  return config;
});

AxiosInstance 的定义(index.d.ts)在 Axios 类之上追加了可调用签名apiClient<User>("/users/1")apiClient<User>({ url: "/users/1" }) 两种写法都有完整泛型推导;create 返回的仍是 AxiosInstance,其 defaults 属性带有 headers 索引签名。默认导出被声明为 AxiosStaticindex.d.ts),它继承 AxiosInstance 并暴露 isCancelisAxiosErrormergeConfigtoFormDataCanceledErrorAxiosHeaders 等工具,因此 axios.getaxios.interceptors 与上述静态函数共享同一套类型。

使用 Symbol 键扩展请求配置

axios 在合并默认配置与单次请求配置时,会保留自有且可枚举的 Symbol 属性。这一行为的实现证据在 lib/core/mergeConfig.js

if (Object.getOwnPropertySymbols && Object.getOwnPropertyDescriptor) {
  return Object.keys(thing).concat(
    Object.getOwnPropertySymbols(thing).filter(
      (symbol) => Object.getOwnPropertyDescriptor(thing, symbol).enumerable
    )
  );
}

即:只有「自有(own)且可枚举(enumerable)」的 Symbol 属性会参与拷贝;继承来的或非可枚举的 Symbol 属性不会被复制。

利用这一点,应用可以通过模块增强(module augmentation)AxiosRequestConfig 增加一个精确的 Symbol 键,并在拦截器/适配器中通过 InternalAxiosRequestConfig 读取:

import axios from "axios";

export const someFlag: unique symbol = Symbol(
  "some flag used in request interceptor"
);

declare module "axios" {
  interface AxiosRequestConfig<D = any, P = any> {
    [someFlag]?: boolean;
  }
}

axios.interceptors.request.use((config) => {
  if (config[someFlag]) {
    config.headers.set("X-Some-Flag", "enabled");
  }
  return config;
});

await axios.get("/users", { [someFlag]: true });

这种方案的优势在于键名是 unique symbol,不会与公共配置字段冲突,且 declare module "axios" 的增强会全局生效于项目内所有引用 axios 类型的文件。

响应数据的泛型标注

axios 的请求方法针对响应数据类型是泛型的。给 axios.get<T>(以及其他别名方法)传入类型参数即可为 response.data 标注类型:

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

const { data } = await apiClient.get<User>("/users/1");
// `data` 的类型为 `User`

配合前面介绍的 DP 参数,一个接口调用可以完整表达四种类别:T(响应)、R(自定义响应结构)、D(请求体)、P(查询参数),例如 apiClient.get<User, void, RequestBody, SearchParams>(url, config)

参考与延伸阅读

适用前提:本文的类型行为以当前仓库 package.json 中的 axios@1.19.0 声明文件为准;moduleResolution: node16 要求 TypeScript 4.7 及以上版本,低版本编译器请按前文表格选择 esModuleInterop 等替代配置。

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