VDone Demo VDone Demo
Home
  • Articles

    • JavaScript
  • Study Notes

    • JavaScript Tutorial
    • Professional JavaScript
    • ES6 Tutorial
    • Vue
    • React
    • TypeScript: Build Axios from Scratch
    • Git
    • TypeScript
    • JS Design Patterns
  • HTML
  • CSS
  • Technical Docs
  • GitHub Tips
  • Node.js
  • Blog Setup
  • Learning
  • Interviews
  • Miscellaneous
  • Practical Tips
  • Friends
About
Bookmarks
  • Categories
  • Tags
  • Archives
GitHub (opens new window)

Nikolay Tuzov

Backend Developer
Home
  • Articles

    • JavaScript
  • Study Notes

    • JavaScript Tutorial
    • Professional JavaScript
    • ES6 Tutorial
    • Vue
    • React
    • TypeScript: Build Axios from Scratch
    • Git
    • TypeScript
    • JS Design Patterns
  • HTML
  • CSS
  • Technical Docs
  • GitHub Tips
  • Node.js
  • Blog Setup
  • Learning
  • Interviews
  • Miscellaneous
  • Practical Tips
  • Friends
About
Bookmarks
  • Categories
  • Tags
  • Archives
GitHub (opens new window)
  • 初识 TypeScript

  • TypeScript 常用语法

  • ts-axios 项目初始化

  • ts-axios 基础功能实现

  • ts-axios 异常情况处理

  • ts-axios 接口扩展

    • Extending Interfaces
      • Requirements Analysis
      • Interface Type Definitions
      • Creating the Axios Class
      • Hybrid Object Implementation
      • Writing the Demo
    • axios Function Overloading
    • Generic Support for Response Data
  • ts-axios 拦截器实现

  • ts-axios 配置化实现

  • ts-axios 取消功能实现

  • ts-axios 更多功能实现

  • ts-axios 单元测试

  • ts-axios 部署与发布

  • 《TypeScript 从零实现 axios》
  • ts-axios 接口扩展
HuangYi
2020-01-05
Contents

Extending Interfaces

# Extending Interfaces

# Requirements Analysis

To make it more convenient for users to send requests with axios, we can extend some interfaces for all supported request methods:

  • axios.request(config)

  • axios.get(url[, config])

  • axios.delete(url[, config])

  • axios.head(url[, config])

  • axios.options(url[, config])

  • axios.post(url[, data[, config]])

  • axios.put(url[, data[, config]])

  • axios.patch(url[, data[, config]])

When using these methods, we no longer need to specify the url, method, or data properties in the config object.

From a requirements perspective, axios is no longer just a function but more like a hybrid object -- it is a function itself and also has many method properties. Next, let's implement this hybrid object.

# Interface Type Definitions

Based on the requirements analysis, the hybrid object axios itself is a function. We will implement a class that includes its method properties, then copy the prototype properties and instance properties of this class onto axios.

Let's first define the interface for the axios hybrid object:

types/index.ts:

export interface Axios {
  request(config: AxiosRequestConfig): AxiosPromise

  get(url: string, config?: AxiosRequestConfig): AxiosPromise

  delete(url: string, config?: AxiosRequestConfig): AxiosPromise

  head(url: string, config?: AxiosRequestConfig): AxiosPromise

  options(url: string, config?: AxiosRequestConfig): AxiosPromise

  post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise

  put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise

  patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise
}

export interface AxiosInstance extends Axios {
  (config: AxiosRequestConfig): AxiosPromise
}

export interface AxiosRequestConfig {
  url?: string
  // ...
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

First, we define an Axios type interface that describes the public methods of the Axios class. Then we define the AxiosInstance interface extending Axios, making it a hybrid type interface.

Additionally, the url property in the AxiosRequestConfig type interface has been changed to an optional property.

# Creating the Axios Class

We create an Axios class to implement the public methods defined in the interface. We create a core directory to house the core request flow code. Inside the core directory, we create the Axios.ts file.

core/Axios.ts

import { AxiosRequestConfig, AxiosPromise, Method } from '../types'
import dispatchRequest from './dispatchRequest'

export default class Axios {
  request(config: AxiosRequestConfig): AxiosPromise {
    return dispatchRequest(config)
  }

  get(url: string, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithoutData('get', url, config)
  }

  delete(url: string, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithoutData('delete', url, config)
  }

  head(url: string, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithoutData('head', url, config)
  }

  options(url: string, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithoutData('options', url, config)
  }

  post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithData('post', url, data, config)
  }

  put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithData('put', url, data, config)
  }

  patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithData('patch', url, data, config)
  }

  _requestMethodWithoutData(method: Method, url: string, config?: AxiosRequestConfig) {
    return this.request(
      Object.assign(config || {}, {
        method,
        url
      })
    )
  }

  _requestMethodWithData(method: Method, url: string, data?: any, config?: AxiosRequestConfig) {
    return this.request(
      Object.assign(config || {}, {
        method,
        url,
        data
      })
    )
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

The request method has the same functionality as our previous axios function. Since the axios function's purpose is to send requests, following modular programming principles, we extract this functionality into a separate module. Inside the core directory, we create the dispatchRequest method and move the relevant code from axios.ts there. We also move the xhr.ts file into the core directory.

core/dispatchRequest.ts:

import { AxiosPromise, AxiosRequestConfig, AxiosResponse } from '../types'
import xhr from './xhr'
import { buildURL } from '../helpers/url'
import { transformRequest, transformResponse } from '../helpers/data'
import { processHeaders } from '../helpers/headers'

export default function dispatchRequest(config: AxiosRequestConfig): AxiosPromise {
  processConfig(config)
  return xhr(config).then(res => {
    return transformResponseData(res)
  })
}

function processConfig(config: AxiosRequestConfig): void {
  config.url = transformURL(config)
  config.headers = transformHeaders(config)
  config.data = transformRequestData(config)
}

function transformURL(config: AxiosRequestConfig): string {
  const { url, params } = config
  return buildURL(url, params)
}

function transformRequestData(config: AxiosRequestConfig): any {
  return transformRequest(config.data)
}

function transformHeaders(config: AxiosRequestConfig) {
  const { headers = {}, data } = config
  return processHeaders(headers, data)
}

function transformResponseData(res: AxiosResponse): AxiosResponse {
  res.data = transformResponse(res.data)
  return res
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

Returning to the Axios.ts file, the get, delete, head, options, post, patch, and put methods are all syntactic sugar exposed to the user. Internally, they all send requests by calling the request method, with the only difference being a layer of config merging before the call.

# Hybrid Object Implementation

The implementation of the hybrid object is straightforward. First, this object is a function; second, it needs to include all prototype and instance properties of the Axios class. Let's start by implementing a helper function called extend.

helpers/util.ts

export function extend<T, U>(to: T, from: U): T & U {
  for (const key in from) {
    ;(to as T & U)[key] = from[key] as any
  }
  return to as T & U
}
1
2
3
4
5
6

The extend method uses intersection types and type assertions. The ultimate goal of extend is to copy all properties from from to to, including prototype properties.

Next, we modify the axios.ts file. We use the factory pattern to create the axios hybrid object.

axios.ts:

import { AxiosInstance } from './types'
import Axios from './core/Axios'
import { extend } from './helpers/util'

function createInstance(): AxiosInstance {
  const context = new Axios()
  const instance = Axios.prototype.request.bind(context)

  extend(instance, context)

  return instance as AxiosInstance
}

const axios = createInstance()

export default axios
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Inside the createInstance factory function, we first instantiate an Axios instance called context, then create instance pointing to the Axios.prototype.request method with context bound as the context. We then use the extend method to copy all prototype and instance methods from context onto instance, achieving the hybrid object: instance is itself a function while also possessing all prototype and instance properties of the Axios class. Finally, we return this instance. Since TypeScript cannot correctly infer the type of instance here, we assert it as the AxiosInstance type.

This way, we create axios through the createInstance factory function. Calling axios directly is equivalent to executing the Axios class's request method to send a request. Of course, we can also call axios.get, axios.post, and other methods.

# Writing the Demo

Create an extend directory under the examples directory, and create index.html inside the extend directory:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Extend example</title>
  </head>
  <body>
    <script src="/__build__/extend.js"></script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10

Then create app.ts as the entry file:

import axios from '../../src/index'

axios({
  url: '/extend/post',
  method: 'post',
  data: {
    msg: 'hi'
  }
})

axios.request({
  url: '/extend/post',
  method: 'post',
  data: {
    msg: 'hello'
  }
})

axios.get('/extend/get')

axios.options('/extend/options')

axios.delete('/extend/delete')

axios.head('/extend/head')

axios.post('/extend/post', { msg: 'post' })

axios.put('/extend/put', { msg: 'put' })

axios.patch('/extend/patch', { msg: 'patch' })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

Then run npm run dev in the command line, open Chrome browser, and visit http://localhost:8080/ to access our demo. Navigate to the Extend directory, and through the developer tools' network panel, we can see the status of each request sent.

At this point, we have supported extending the axios API and turned it into a hybrid object. The official axios instance supports not only axios(config) but also passing 2 arguments axios(url, config), which involves the concept of function overloading. In the next section, we will implement this feature.

Edit (opens new window)
#TypeScript
Last Updated: 2026/03/21, 12:14:36
Enhanced Error Information
axios Function Overloading

← Enhanced Error Information axios Function Overloading→

Recent Updates
01
How I Discovered Disposable Email — A True Story
06-12
02
Animations in Grid Layout
09-15
03
Renaming a Git Branch
08-11
More Articles >
Theme by VDone | Copyright © 2026-2026 Nikolay Tuzov | MIT License | Telegram
  • Auto
  • Light Mode
  • Dark Mode
  • Reading Mode