0. 系列文章

1.使用Typescript重构axios(一)——写在最前面

2.使用Typescript重构axios(二)——项目起手,跑通流程

3.使用Typescript重构axios(三)——实现基础功能:处理get请求url参数

4.使用Typescript重构axios(四)——实现基础功能:处理post请求参数

5.使用Typescript重构axios(五)——实现基础功能:处理请求的header

6.使用Typescript重构axios(六)——实现基础功能:获取响应数据

7.使用Typescript重构axios(七)——实现基础功能:处理响应header

8.使用Typescript重构axios(八)——实现基础功能:处理响应data

9.使用Typescript重构axios(九)——异常处理:基础版

10.使用Typescript重构axios(十)——异常处理:增强版

11.使用Typescript重构axios(十一)——接口扩展

12.使用Typescript重构axios(十二)——增加参数

13.使用Typescript重构axios(十三)——让响应数据支持泛型

14.使用Typescript重构axios(十四)——实现拦截器

15.使用Typescript重构axios(十五)——默认配置

16.使用Typescript重构axios(十六)——请求和响应数据配置化

17.使用Typescript重构axios(十七)——增加axios.create

18.使用Typescript重构axios(十八)——请求取消功能:总体思路

19.使用Typescript重构axios(十九)——请求取消功能:实现第二种使用方式

20.使用Typescript重构axios(二十)——请求取消功能:实现第一种使用方式

21.使用Typescript重构axios(二十一)——请求取消功能:添加axios.isCancel接口

22.使用Typescript重构axios(二十二)——请求取消功能:收尾

23.使用Typescript重构axios(二十三)——添加withCredentials属性

24.使用Typescript重构axios(二十四)——防御XSRF攻击

25.使用Typescript重构axios(二十五)——文件上传下载进度监控

26.使用Typescript重构axios(二十六)——添加HTTP授权auth属性

27.使用Typescript重构axios(二十七)——添加请求状态码合法性校验

28.使用Typescript重构axios(二十八)——自定义序列化请求参数

29.使用Typescript重构axios(二十九)——添加baseURL

30.使用Typescript重构axios(三十)——添加axios.getUri方法

31.使用Typescript重构axios(三十一)——添加axios.all和axios.spread方法

32.使用Typescript重构axios(三十二)——写在最后面(总结)

项目源码请猛戳这里!!!

1. 前言

虽然我们目前已经实现了axios的所有基础功能以及异常情况的处理,但是我们实现的axios在使用起来好像只能向函数调用一样使用:

axios({
method: 'post',
url: '/base/post',
data: {
a: 1,
b: 2
}
})

而官方的axios不但可以这样使用,它还对外提供了很多接口,如:

  • 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]])

有了这些接口,就可以让我们省去一些配置,能够很方便的使用。那么我们接下来也要来实现这些接口。

2. 前置知识:混合对象

通过上面分析,我们发现:我们可以把axios对象当函数一样调用,也可以从它身上点出来一系列方法调用。这种设计使得axios更像是一个混合对象,本身是一个方法,内部又有很多方法属性。

所谓混合对象,我们可以看下面这段代码:

function getCounter() {
let counter = function (num) { console.log(num) }
counter.interval = 123
counter.reset = function () { console.log('reset')}
return counter
} let c = getCounter()
c(10) // 10
c.reset() // reset
c.interval = 5.0
console.log(c.interval) // 5

getCounter函数内部返回了一个函数counter,并且在counter上面挂载了一些属性,这就使得counter函数变成了一个混合对象,它既能够当函数一样调用,本身又有了很多方法属性。

3. 实现思路

仿照上面这个例子,我们接下来扩展axios接口就可以这样做:

  • 我们先创建一个axios类,在类内部实现我们要的所有的接口,包括requestgetpostdelete等等;

  • 然后我们创建一个类似于getCountergetAxios的函数;

    function getAxios() {
    let axios = function () { } //之前创建的axios方法
    axios.reuqest = ''
    axios.get = ''
    axios.post = ''
    // ...
    return axios
    }
  • getAxios函数内部给之前创建的axios方法上挂载我们要的接口,然后把axios返回;

  • 这样我们在外面就能把axios既能当函数用,又能点出来其他的接口方法属性。

OK,这就是实现思路,话不多说,开干!。

4. 定义Axios类类型接口

定义Axios类之前,我们先在src/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 // 以下三个与上面三个多了data参数 post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise
}

这里有一个问题:

有了这些接口以后,我们将来发getpost请求时,我们只需这样:

axios.get(url,config/*除url、method外的其他配置*/)
axios.post(url,data,config/*除url、method、data外的其他配置*/)

config中我们就不需要再配置urlmethoddata了,而在上面的接口定义中,config的类型仍是AxiosRequestConfig,而我们当时定义AxiosRequestConfig接口类型时的url是必选参数,所以我们现在就要将它改成可选参数了,如下:

export interface AxiosRequestConfig {
url?: string;
method?: Method;
headers?: any;
data?: any;
params?: any;
responseType?: XMLHttpRequestResponseType;
timeout?: number;
}

定义好之后,我们顺便再来定义一下将来的混合对象axios的类型接口:

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

OK,接口类型就定义完毕了。

5. 创建Axios类

现在,我们就可以来创建Axios类,在其内部实现我们想要的所有对外接口的方法了。

我们在src下面创建一个core目录,用来存放发送请求核心流程的代码。我们将之前src/xhr.tssrc/index.ts文件一并移入src/core目录内,并且为了区分将来的axios混合对象,我们将之前在src/index.ts中写的axios函数的函数名与文件名改为dispatchRequestdispatchRequest.ts。改过之后,要将之前所有引用过这几个文件和函数的地方都要做一下更改,建议使用webstorm开发,可以一键自动更改所有引用地方,非常方便。

我们在src/core目录下创建Axios.ts文件,在该文件内创建Axios类:

// src/core/Axios.ts

import { AxiosPromise, AxiosRequestConfig } 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.request(
Object.assign(config || {}, {
method: "get",
url
})
);
} delete(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "delete",
url
})
);
} head(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "head",
url
})
);
} options(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "options",
url
})
);
} post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "post",
url,
data
})
);
} put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "put",
url,
data
})
);
} patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "patch",
url,
data
})
);
}
}

写完之后,我们发现:其实getdeleteheadoptionspostpatchput 这些接口方法,内部都是通过调用 request 方法实现发送请求,只不过在调用之前将请求方法methoddata使用Object.assign合并进 config内。另外,我们还发现:getdeleteheadoptions这四个方法是不需要data参数的,并且它们内部实现的代码几乎一模一样,而postpatchput 这三个方法是需要data参数的,而且它们三个内部实现的代码也几乎一样,所以本着面向对象的原则,我们将其分别封装为两个子函数:

_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
})
)
}

然后我们就可以在getdeleteheadoptions这四个方法里调用_requestMethodWithoutData,在postpatchput 方法里调用_requestMethodWithDataAxios类改写如下:

import { AxiosPromise, AxiosRequestConfig, 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
})
);
}
}

OK,Axios类就已经实现好了。

6. 创建混合对象axios

接下来,我们就可以按照第3步的实现思路来实现这个混合对象axios了。我们在src下新创建一个axios.ts文件(之前的src/axios.ts文件已被移入src/core目录下并改名为dispatchRequest),在该文件内我们来创建混合对象axios,如下:

  • 首先我们先来创建一个类似于第2节的getCountergetAxios的函数,它将返回最终的混合对象axios,所以它的返回值类型为之前创建的AxiosInstance

    function getAxios(): AxiosInstance {
    
      // ...  
    
      return axios
    }
  • 接着,我们在getAxios函数内部创建类似于第2节的counteraxios的函数,这里的axios函数其实就是Axios类中的request方法;

    function getAxios(): AxiosInstance {
    const axios = Axios.prototype.request
    // ... return axios
    }
  • 这里,我们还需要注意一点,我们需要把Axios类的实例对象绑定给axios函数的上下文this。这是因为混合对象在axios.get使用时,其实是调用了Axios类中的get方法,而get方法内部是调用了this._requestMethodWithoutData,所以我们需要把Axios类的实例对象绑定给axios函数的上下文this,不然它就找不到this._requestMethodWithoutData

    function getAxios(): AxiosInstance {
    const context = new Axios()
    const axios = Axios.prototype.request.bind(context)
    // ... return axios
    }
  • 然后,我们就可以给axios上面挂载我们所需要的所有接口了

    function getAxios(): AxiosInstance {
    const context = new Axios()
    const axios = Axios.prototype.request.bind(context)
    // 挂载接口
    axios.get = Axios.prototype.get.bind(context);
    axios.post = Axios.prototype.post.bind(context);
    axios.delete = Axios.prototype.delete.bind(context);
    axios.put = Axios.prototype.put.bind(context); // ...剩下的所有接口
    return axios
    }
  • 所有接口挂载好之后,就到最后一步,执行getAxios函数,返回混合对象axios了;

    function getAxios(): AxiosInstance {
    const context = new Axios();
    const axios = Axios.prototype.request.bind(context); // 挂载接口
    axios.get = Axios.prototype.get.bind(context);
    axios.post = Axios.prototype.post.bind(context);
    axios.delete = Axios.prototype.delete.bind(context);
    axios.put = Axios.prototype.put.bind(context); // ...剩下的所有接口
    return axios as AxiosInstance;
    } const axios = getAxios();
    export default axios;

这样,我们的混合对象axios 就创建好了,但是你肯定发现,我们在getAxios函数内部挂载接口的时候,写了很多重复的代码,其实我们可以写一个工具函数extand,来帮助我们完成那一堆接口的挂载,所以我们在src/helpers/util.ts文件内创建extend函数,如下:

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;
}

extend 方法的实现用到了交叉类型,并且用到了类型断言。extend 的最终目的是把 from 里的属性都扩展到 to 中,包括原型上的属性。

创建好之后,我们就可以在getAxios函数内部使用extend方法了:

import { AxiosInstance } from "./types";
import Axios from "./core/Axios";
import { extend } from "./helpers/util"; function getAxios(): AxiosInstance {
const context = new Axios();
const axios = Axios.prototype.request.bind(context); extend(axios, context); return axios as AxiosInstance;
} const axios = getAxios(); export default axios;

OK,至此,混合对象axios就已经创建完毕了,当直接调用 axios 方法就相当于执行了 Axios 类的 request 方法发送请求,当然我们也可以调用 axios.getaxios.post 等方法。接下来,我们就可以编写demo来测试下我们的成果。

7. demo编写

examples 目录下创建 expandInterface目录,在 expandInterface目录下创建 index.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>expandInterface demo</title>
</head>
<body>
<script src="/__build__/expandInterface.js"></script>
</body>
</html>

接着再创建 app.ts 作为入口文件:

import axios from "../../src/axios";

axios({
url: '/api/expandInterface/post',
method: 'post',
data: {
msg: 'hi'
}
}) axios.request({
url: '/api/expandInterface/post',
method: 'post',
data: {
msg: 'hello'
}
}) axios.get('/api/expandInterface/get') axios.options('/api/expandInterface/options') axios.delete('/api/expandInterface/delete') axios.head('/api/expandInterface/head') axios.post('/api/expandInterface/post', { msg: 'post' }) axios.put('/api/expandInterface/put', { msg: 'put' }) axios.patch('/api/expandInterface/patch', { msg: 'patch' })

接着在 server/server.js 添加新的接口路由:

// 扩展接口
router.get("/api/expandInterface/get", function(req, res) {
res.json({
msg: "hello world"
});
}); router.options("/api/expandInterface/options", function(req, res) {
res.end();
}); router.delete("/api/expandInterface/delete", function(req, res) {
res.end();
}); router.head("/api/expandInterface/head", function(req, res) {
res.end();
}); router.post("/api/expandInterface/post", function(req, res) {
res.json(req.body);
}); router.put("/api/expandInterface/put", function(req, res) {
res.json(req.body);
}); router.patch("/api/expandInterface/patch", function(req, res) {
res.json(req.body);
});

最后在根目录下的index.html中加上启动该demo的入口:

<li><a href="examples/expandInterface">expandInterface</a></li>

OK,我们在命令行中执行:

# 同时开启客户端和服务端
npm run server | npm start

接着我们打开 chrome 浏览器,访问 http://localhost:8000/ 即可访问我们的 demo 了,我们点击 expandInterface,通过F12network 部分我们可以看到所有的请求都已正常发出:

OK,接口扩展我们就已经实现了。

(完)

最新文章

  1. STSdb,最强纯C#开源NoSQL和虚拟文件系统 4.0 RC2 支持C/S架构
  2. 演示save point及自治事务的用处
  3. android 总结(样式)—跑马灯 button的点击效果 RadioGroup 实现滑动的效果 button 下面有阴影 卡片样式
  4. 缺jstl.jar包导致的代码出现异常
  5. CDH5 集群安装教程
  6. iphone手机用wireshark抓包
  7. 【Android Developers Training】 49. 轻松录制视频
  8. Android View的事件冲突
  9. B20J_2836_魔法树_树链剖分+线段树
  10. 新系统添加sshkey/pexpect基本使用
  11. 关于video标签移动端开发遇到的问题,获取视频第一帧,全屏,自动播放,自适应等问题
  12. 数字特征值-java
  13. Windows下安装flask虚拟环境
  14. 【20190129】CSS-定位问题记录
  15. dedecms获取顶级栏目名称、二级栏目名称实现方法 转
  16. 【LeetCode每天一题】Reverse String
  17. elasticsearch 口水篇(2)CRUD Sense
  18. Django-基本概念
  19. C++ STL set和multiset的使用
  20. PHP mongodb 的使用

热门文章

  1. WebGL简易教程(十一):纹理
  2. 信息传递 NOIP2015 day1 T2
  3. Kubernetes之Flannel介绍
  4. 设置Linux支持中文
  5. Mqtt-Client
  6. PowerShell渗透--Empire(三)
  7. 2. Rsync-远程同步(上)
  8. window下设置定时任务及基本配置
  9. WSL捣鼓记——图形化(以emacs为例)
  10. VMware安装和linux(centos7)系统安装