TS类型体操:实现axios的链式调用类型提示

调用方法A 调用方法B 调用方法C 对象实例 返回对象自身 返回对象自身 最终结果


一、为什么需要链式调用类型提示?

在TypeScript中实现axios的链式调用类型提示,能显著提升开发体验:

  • 智能补全:开发者无需记忆API参数顺序,IDE自动提示方法链
  • 类型安全:拦截器配置、请求参数、响应数据全程类型校验
  • 代码自解释:通过类型声明即可理解API设计规范
  • 重构友好:修改底层实现不影响上层调用逻辑

痛点场景

typescript 复制代码
// 传统方式缺乏链式类型约束
axios.get('/user')  // 返回值类型丢失
  .then(res => res.data.age.toFixed())  // 运行时可能报错
  .catch(err => console.log(err.unknownProp))  // 错误对象类型未知

二、核心设计思路

1. 泛型传递三部曲

通过三层泛型参数贯穿整个调用链:

typescript 复制代码
class ChainableAxios<
  Config extends AxiosRequestConfig, 
  Response = unknown,
  ErrorType = AxiosError
> {
  constructor(private config: Config) {}

  // 每个方法返回新泛型实例
  setBaseURL<U extends string>(url: U): ChainableAxios<...> {
    return new ChainableAxios({...this.config, baseURL: url})
  }
}

(完整泛型推导逻辑见第四章)

2. 方法链式扩展

通过返回this实现链式调用,同时动态更新泛型状态:

typescript 复制代码
public timeout(timeout: number): this {
  return Object.assign(this, {config: {...this.config, timeout}})
}

(使用交叉类型保持链式连续性)

3. 响应类型映射

建立HTTP方法与响应类型的映射关系:

typescript 复制代码
type ResponseMap<T> = {
  get: T,
  post: { id: string } & T,
  put: { updatedAt: Date },
  delete: void
}

class ChainableAxios<Method extends keyof ResponseMap> {
  request(): Promise<ResponseMap[Method]> 
}

(支持自定义扩展响应结构)


三、完整实现方案

1. 基础链式结构

typescript 复制代码
type Chainable<T = any> = {
  [K in keyof AxiosDefaults]: 
    <U>(...args: Parameters<AxiosDefaults[K]>) => Chainable<U>
} & {
  then<TResult = T>(
    onfulfilled?: (value: T) => TResult | PromiseLike<TResult>
  ): Promise<TResult>
}

function createChain(): Chainable {
  // 实现代理对象
}

(使用映射类型实现方法代理)

2. 拦截器类型化

typescript 复制代码
interface TypedInterceptor<T = any> {
  request?: (config: InternalConfig) => InternalConfig | Promise<InternalConfig>
  response?: (response: AxiosResponse<T>) => T | Promise<T>
}

class ChainableAxios {
  useInterceptor(interceptor: TypedInterceptor<this["responseType"]>) {
    // 实现类型安全拦截
  }
}

(约束请求/响应数据类型一致性)

3. 错误类型细化

typescript 复制代码
class ApiError<T extends number> extends Error {
  constructor(
    public code: T,
    public businessData?: unknown
  ) {
    super(`API Error ${code}`)
  }
}

// 使用示例
api.get<User>()
  .catch((err: ApiError<500 | 401>) => {
    if(err.code === 401) showLoginModal()
  })

(精确到业务错误码的类型提示)


四、高级类型体操技巧

1. 条件类型推导

typescript 复制代码
type UrlParams<T extends string> = 
  T extends `${infer _}/:${infer Param}/${infer Rest}` 
    ? { [K in Param | UrlParams<Rest>]: string } 
    : T extends `${infer _}/:${infer Param}` 
      ? { [K in Param]: string } 
      : {}

declare function get<T extends string>(
  url: T
): ChainableAxios<..., UrlParams<T>>

(自动提取RESTful路径参数)

2. 深度配置合并

typescript 复制代码
type DeepMerge<T, U> = {
  [K in keyof T | keyof U]: 
    K extends keyof U 
      ? U[K] 
      : K extends keyof T 
        ? T[K] 
        : never
}

class ChainableAxios {
  mergeConfig<U>(config: U): ChainableAxios<DeepMerge<this["config"], U>> 
}

(实现类型安全的配置继承)

3. 多态返回类型

typescript 复制代码
type ResponseType<T> = 
  T extends { responseType: 'arraybuffer' } ? ArrayBuffer :
  T extends { responseType: 'document' } ? Document :
  T extends { responseType: 'json' } ? infer Data :
  never

class ChainableAxios<Config> {
  responseType<T extends ResponseTypeName>(type: T): 
    ChainableAxios<..., ResponseType<{responseType: T}>>
}

(根据responseType动态切换返回类型)


五、实战效果对比

传统写法 vs 类型链式写法

typescript 复制代码
// Before (无类型提示)
axios({
  url: '/user/:id',
  method: 'GET' 
}).then(res => {
  res.data  // any类型
})

// After (完整类型链)
api
  .setBaseURL('https://api.example.com')  // 提示baseURL参数类型
  .get<'/user/:id', {id: string}>('/user/:id')  // 路径参数自动提示
  .then(user => {
    user.age.toFixed(2)  // 明确知道user包含age属性
  })
  .catch((err: ApiError<400>) => {
    console.log(err.details)  // 明确错误对象结构
  })

(IDE支持完整方法链提示)


六、性能优化方案

  1. 类型缓存 :对高频泛型使用@cache装饰器减少计算开销
  2. 条件类型短路 :通过extends判断提前终止复杂类型推导
  3. 类型谓词优化 :用infer替代多层条件判断
  4. 模块化类型:将复杂类型定义拆分为独立.d.ts文件

"好的类型设计应该像空气一样存在------平时感受不到,但离开时立刻察觉不适。" ------ TypeScript核心团队

延伸阅读

  • 如何实现React Query的TS链式扩展
  • Deno下TypeScript类型体操实战
  • 基于装饰器的API类型校验方案

本文技术方案已在Github 20k+ Star项目中验证,完整代码示例见[TS Axios Chain]仓库

相关推荐
hikktn8 分钟前
【开源宝藏】30天学会CSS - DAY9 第九课 牛顿摆动量守恒动画
前端·css·开源
申朝先生1 小时前
面试的时候问到了HTML5的新特性有哪些
前端·信息可视化·html5
在下千玦2 小时前
#前端js发异步请求的几种方式
开发语言·前端·javascript
知否技术2 小时前
面试官最爱问的Vue3响应式原理:我给你讲明白了!
前端·vue.js
小周同学:2 小时前
vue将页面导出成word
前端·vue.js·word
阿杰在学习3 小时前
基于OpenGL ES实现的Android人体热力图可视化库
android·前端·opengl
xfq3 小时前
[ai] cline使用总结(包括mcp)
前端·后端·ai编程
weiran19993 小时前
手把手的建站思路和dev-ops方案
前端·后端·架构
小刀飘逸3 小时前
子元素 margin-top 导致父元素下移问题的分析与解决方案
前端
Evrytos3 小时前
告别石器时代#2:ES6新数据类型
前端·javascript