首页
/ Axios 实例创建完全指南:深入 axios.create() 的预配置与隔离机制

Axios 实例创建完全指南:深入 axios.create() 的预配置与隔离机制

2026-09-05 09:09:19作者:晏闻田Solitary

本文基于官方文档 Creating an instance 展开,系统讲解 Axios 中 axios.create() 实例机制:如何为不同服务创建预配置实例、实例与默认对象的关系、按请求覆盖默认值的优先级规则,并结合仓库源码揭示 createInstance 的工厂实现、配置合并(mergeConfig)与拦截器隔离的底层原理,帮助你在多 API 应用中建立清晰可维护的请求分层。

axios.create() 是什么

axios.create() 用于创建一个预配置好的 axios 实例。该实例与默认的 axios 对象共享同一套请求/响应 API,但会把你在创建时传入的配置作为该实例每一次请求的基线(baseline)。官方文档明确建议:对于任何超出单文件规模的应用,都推荐使用实例方式使用 axios。

基本用法如下:

import axios from "axios";

const instance = axios.create({
  baseURL: "https://api.example.com",
  timeout: 5000,
  headers: { "X-Custom-Header": "foobar" },
});

create 方法接受完整的请求配置(Request Config)对象。创建之后,你可以像使用默认 axios 对象一样使用该实例:

const response = await instance.get("/users/1");

源码视角:createInstance 工厂函数

lib/axios.js 中,实例并非由 class 直接 new 出来的“裸对象”,而是由 createInstance 工厂函数组装而成:

function createInstance(defaultConfig) {
  const context = new Axios(defaultConfig);
  const instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  utils.extend(instance, Axios.prototype, context, { allOwnKeys: true });

  // Copy context to instance
  utils.extend(instance, context, null, { allOwnKeys: true });

  // Factory for creating new instances
  instance.create = function create(instanceConfig) {
    return createInstance(mergeConfig(defaultConfig, instanceConfig));
  };

  return instance;
}

这段代码揭示了三个关键事实:

  1. 实例本身就是一个被绑定的 request 函数instance(url, config) 这种“类 fetch”的直接调用方式之所以可行,是因为 instance 直接就是 Axios.prototype.request 绑定到 context 后的函数引用;
  2. API 的完整继承来自两次 utils.extend。先把 Axios.prototype 上的所有方法(getpostdeleteputpatchquerygetUri 等)复制到实例上,再把 context(即 Axios 实例本体)复制到实例上——这就是文档所说“实例与默认对象共享同一 API”的实现来源。浏览器端测试 tests/browser/instance.browser.test.js 中的 should have the same methods as default instance 用例正是逐属性断言了实例与默认对象的方法类型一致性;
  3. 实例可以递归创建instance.create(instanceConfig) 会用 mergeConfig(defaultConfig, instanceConfig) 把父实例的默认配置与新配置合并后再调用 createInstance。这意味着你可以在一个基础实例上派生出配置更细化的子实例,且父实例的默认值会被自动继承——这是构建“配置树”式 API 客户端的底层能力。

再看 lib/core/Axios.js 中的 Axios 类构造函数:

class Axios {
  constructor(instanceConfig) {
    this.defaults = instanceConfig || {};
    this.interceptors = {
      request: new InterceptorManager(),
      response: new InterceptorManager(),
    };
  }
}

这里有两个要点:传入的配置(或空对象)被原样保存在 this.defaults 上——这就是后文“运行时修改 instance.defaults”能生效的存储位置;同时每个 Axios 上下文都持有独立的 request / response 两个 InterceptorManager——这是“拦截器隔离”这一特性的直接来源。

另外值得注意的是 lib/axios.js 中的一行:const axios = createInstance(defaults);——导出的默认 axios 对象本身也是通过同一个工厂创建的,只不过它的基线配置来自 lib/defaults/index.js 中的全局 defaults(例如 timeout: 0 表示默认不超时、validateStatus 默认只将 200~299 视为成功、headers.common 中默认 Accept: application/json, text/plain, */* 等)。因此“实例”与“默认对象”在架构上是同构的,区别仅在于 defaults 的内容。

为什么要用实例?四个典型场景

场景一:按服务划分 base URL

大多数应用会与不止一个 API 交互。为每个服务创建独立实例,可以避免在每次调用中重复书写基础 URL:

const githubApi = axios.create({ baseURL: "https://api.github.com" });
const internalApi = axios.create({ baseURL: "https://api.internal.example.com" });

const { data: repos } = await githubApi.get("/users/axios/repos");
const { data: users } = await internalApi.get("/users");

场景二:共享的认证请求头

把认证 token 挂到某个实例上,即可让该实例的每个请求自动携带 token,且不会影响其他实例:

const authApi = axios.create({
  baseURL: "https://api.example.com",
  headers: {
    Authorization: `Bearer ${getToken()}`,
  },
});

场景三:按服务区分超时(与重试策略)

不同服务的可靠性特征不同。为实时服务设置较紧的超时,为批处理任务设置较宽松的超时:

const realtimeApi = axios.create({ baseURL: "https://realtime.example.com", timeout: 2000 });
const batchApi    = axios.create({ baseURL: "https://batch.example.com",    timeout: 60000 });

这里有一个容易踩坑的细节:全局默认的 timeout0lib/defaults/index.js 中注释说明 “If set to 0 (default) a timeout is not created”),即不设置超时。如果你在某个实例上显式设置 timeout: 0,效果等同于取消该实例的超时保护——因此“实时服务 2 秒 / 批处理 60 秒”这类按服务差异化的配置,比在每个调用点零散传递更可控。测试用例 tests/browser/instance.browser.test.js 中的 should use instance options 验证了 axios.create({ timeout: 1000 }) 确实会把超时值作用到实际发出的请求上。

场景四:隔离的拦截器

添加到某个实例上的拦截器只对该实例生效,从而保持各模块职责分离:

const loggingApi = axios.create({ baseURL: "https://api.example.com" });

loggingApi.interceptors.request.use((config) => {
  console.log(`→ ${config.method?.toUpperCase()} ${config.url}`);
  return config;
});

从源码看这种隔离是如何保证的:Axios 构造函数为每个上下文创建了各自独立的 InterceptorManager(见上文 lib/core/Axios.js),而 _request 中组装拦截器链时只遍历 this.interceptors.request / this.interceptors.responselib/core/Axios.js)——this 指向哪个实例,就只收集哪个实例注册过的拦截器。InterceptorManagerlib/core/InterceptorManager.js)内部维护自己的 handlers 数组与内部状态(use 返回递增 id、eject(id) 按 id 移除、clear() 清空全部),因此即使两个实例都在做日志、鉴权等事情,它们的拦截器栈也互不干扰。use 还支持 synchronousrunWhen 选项(lib/core/InterceptorManager.js),其中 runWhen(config) === false 时该拦截器会被本次请求跳过(lib/core/Axios.js),适合给实例级拦截器做条件开关。

按请求覆盖实例默认值

在请求发生时刻传入的配置,其优先级始终高于实例默认值。这一规则直接体现在 lib/core/Axios.js_request 方法中:

_request(configOrUrl, config) {
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof configOrUrl === 'string') {
    config = config || {};
    config.url = configOrUrl;
  } else {
    config = configOrUrl || {};
  }

  config = mergeConfig(this.defaults, config);
  // ...
}

mergeConfig(this.defaults, config) 以实例默认值为底层、以请求级配置为上层执行合并,请求侧的显式取值会覆盖实例侧的同名值。对应文档中的示例:

const api = axios.create({ timeout: 5000 });

// This specific request uses a 30-second timeout instead
await api.get("/slow-endpoint", { timeout: 30000 });

方法别名(getpostputpatchdeleteheadoptionsquerypostForm*Form 简写)在生成时也会先做一次 mergeConfiglib/core/Axios.js),把 methodurldata 与调用方传入的配置合并后统一交给 request,最终仍汇入上面这条合并链路。相关合并行为的单元测试见 tests/unit/core/mergeConfig.test.js,其中验证了以全局 defaults 为底层合并时,返回值是新对象且 headers 是独立副本(merged.headers).not.toBe(defaults.headers))——也就是说合并不会原地修改实例默认值,请求级的修改不会“污染”实例。

另外两点与 URL 解析相关的行为也来自请求级配置优先原则:实例配置里的 url 不会覆盖调用时传入的 url(测试 tests/browser/instance.browser.test.js 验证了这一点);而 baseURL 与请求 url 的最终拼接发生在 buildFullPath(经由 dispatchRequest 调用),getUri(config) 方法(lib/core/Axios.js)也执行同样的 mergeConfig(this.defaults, config) 后再调用 buildFullPath + buildURL,可用于在不发请求的情况下预计算最终 URL(例如调试 query 序列化)。

创建之后修改 instance.defaults

文档的 tip 指出:实例默认值也可以在创建后修改,只需直接写 instance.defaults

instance.defaults.headers.common["Authorization"] = `Bearer ${newToken}`;

这之所以可行,是因为 defaults 就是构造函数中保存的那个普通对象(this.defaults = instanceConfig || {}),而每次请求的 _request 都会实时读取 this.defaults 参与 mergeConfig,而不是在创建时做一次性快照。典型的适用场景是 token 刷新:拿到 newToken 后更新 headers.common,后续所有走该实例的请求都会自动携带新 token,无需重建实例。

结合 lib/defaults/index.js 的默认请求头结构,你可以了解到 instance.defaults.headers 的组织方式:除 common(对所有方法生效)外,还包含 deletegetheadpostputpatchquery 各自的命名空间,用于按 HTTP 方法细分请求头。在请求管线中,这些方法专属的键会在合并后被扁平化并清理(lib/core/Axios.jsheaders.commonheaders[method] 合并后删除方法键),最终交由 AxiosHeaders.concat 生成规范化请求头。

小结

  • axios.create(config) 返回一个由 createInstance 工厂组装的实例:被绑定的 request 函数 + 完整的方法集合 + 独立的 defaultsinterceptorslib/axios.jslib/core/Axios.js);
  • 官方推荐场景:按服务划分 baseURL、共享认证头、按服务差异化 timeout、拦截器隔离;
  • 优先级规则:请求级配置 > 实例默认值,合并逻辑在 _request 中由 mergeConfig 统一执行;
  • instance.create() 支持基于父实例配置派生子实例(自动 mergeConfig 继承);
  • instance.defaults 是实时生效的引用,可在 token 刷新等场景下原地更新。

如果想进一步掌握实例可传入的完整配置项,参见请求配置文档;拦截器的详细机制(synchronousrunWhen、移除与清理)参见拦截器文档;默认配置的完整清单见 lib/defaults/index.js

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