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. 前言
在大型项目开发中,基于模块化开发的思想,我们往往不会把所有的请求操作直接写入逻辑内,而是将所有的请求按照需求不同分门别类的统一放在一个地方,例如博主在手头的项目开发中,会在项目目录下新建一个叫做 api 的文件夹,在该文件夹内根据业务模块的不同放置不同的请求文件,例如,关于用户增删改查的请求会在 api 文件夹内新建一个 user.ts 文件,然后将四个请求放入该文件;关于日志增删改查的请求会放入 log.ts 文件中,如下所示:
├── api ├── user.ts // 用户相关的请求 ├── log.ts // 日志相关的请求 ...
而在每个文件中,也会把每个请求抽离成单独函数导出,供需要的地方调用,如在 user.ts 中:
// 获取用户 export function getUser() { return axios.get('/getuser') .then(res => res.data) .catch(err => console.error(err)) } // 创建用户 export function createUser(data) { return axios.post('/createUser',data) .then(res => res.data) .catch(err => console.error(err)) } // 删除用户 export function deleteUser() { return axios.delete('/deleteUser') .then(res => res.data) .catch(err => console.error(err)) } // ...
这样做的好处是,所有请求能够被集中统一的管理起来,如果日后有变动也可以快速的找到。
前言说了这么多,还是没有引入正题,其实博主是想说:当我们发出请求后,我们最关心的一个是请求是否成功,另外一个就是返回的响应数据是不是我们想要的,我们能否预先定义一个期望返回的数据类型接口,然后看返回的响应数据能否匹配预先定义的接口,就能够得知返回的数据是不是我们想要的?答案当然是可以的。
2. 需求分析
我们之前给所有的请求响应都规定一个类型接口,我们规定,所有的请求返回的响应都应该包含以上几个部分,如下:
export interface AxiosResponse { data: any; // 服务端返回的数据 status: number; // HTTP 状态码 statusText: string; // 状态消息 headers: any; // 响应头 config: AxiosRequestConfig; // 请求配置对象 request: any; // 请求的 XMLHttpRequest 对象实例 }
但是仅仅是这样还不够,我们还希望能够细化到返回的数据 data 上,例如,当我们获取用户时发出 getUser 请求时,我们希望返回的数据 data 应该是 {name:'难凉热血',age:'18'} 这样的,由于每个请求期望返回的 data 不尽相同,那么我们就应该在发出请求时带上我们想要的 data 类型接口,当数据 data 返回时去匹配我们所携带的接口看是否匹配得上,进而确保是我们想要的数据。
那么,这就要求我们上面定义的 AxiosResponse 接收一个泛型参数,这个参数就是返回数据 data 参数,如下:
export interface AxiosResponse<T = any> { data: T; // 服务端返回的数据 status: number; // HTTP 状态码 statusText: string; // 状态消息 headers: any; // 响应头 config: AxiosRequestConfig; // 请求配置对象 request: any; // 请求的 XMLHttpRequest 对象实例 }
这里我们给 AxiosResponse 接口添加了泛型参数 T , T=any 表示泛型的类型参数默认值为 any 。
有了这个泛型参数以后,我们在发送请求时就可以指定返回的 data 的类型了,如下:
// 获取用户api export function getUser<T>() { return axios.get<ResponseData<T>>('/getuser') .then(res => res.data) .catch(err => console.error(err)) } // 调用getUser发出请求 // 期望返回data的类型 interface User { name: string age: number } async function test() { // user 被推断出为 // { // data: { name: string, age: number }, // ... // } const user = await getUser<User>() }
3. 接口添加泛型参数
接下来,我们就为之前定义好的所有接口都加上泛型参数。
// src/types/index.ts export interface AxiosPromise<T=any> extends Promise<AxiosResponse<T>> {} export interface Axios { request<T=any>(config: AxiosRequestConfig): AxiosPromise<T>; get<T=any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>; delete<T=any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>; head<T=any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>; options<T=any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>; post<T=any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>; put<T=any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>; patch<T=any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>; } export interface AxiosInstance extends Axios { <T=any>(config: AxiosRequestConfig): AxiosPromise<T>; <T=any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>; }
我们为 AxiosPromise 、 Axios 以及 AxiosInstance 接口都加上了泛型参数。我们可以看到这些请求的返回类型都变成了 AxiosPromise<T> ,也就是 Promise<AxiosResponse<T>> ,这样我们就可以从响应中拿到了类型 T 了。
OK,响应数据的泛型参数就已经添加好了,接下里,就可以编写 demo 来测试一下效果。
4. demo编写
在 examples 目录下创建 addGenericityToAxiosResponse 目录,在 addGenericityToAxiosResponse 目录下创建 index.html :
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>addGenericityToAxiosResponse demo</title> </head> <body> <script src="/__build__/addGenericityToAxiosResponse.js"></script> </body> </html>
接着再创建 app.ts 作为入口文件:
import axios from "src/axios"; interface User { name: string; age: number; } function getUser<T>() { return axios<T>("/api/getuser") .then(res => res) .catch(err => console.error(err)); } async function userList() { const user = await getUser<User>(); if (user) { console.log(user.data.name); } } userList();
我们看到,在编写代码的时候, TypeScript 已经可以帮我们推断出 user 中我们预先定义好的想要的数据了。
接着在 server/server.js 添加新的接口路由:
// 响应支持泛型 router.get("/api/getuser", function(req, res) { res.json({ msg: "hello world", data: { name: "难凉热血", age: 18 } }); });
最后在根目录下的 index.html 中加上启动该 demo 的入口:
<li><a href="examples/addGenericityToAxiosResponse">addGenericityToAxiosResponse</a></li>
OK,我们在命令行中执行:
# 同时开启客户端和服务端 npm run server | npm start
接着我们打开 chrome 浏览器,访问 http://localhost:8000/ 即可访问我们的 demo 了,我们点击 addGenericityToAxiosResponse ,通过 F12 的 network 部分我们可以看到请求已正常发出:
OK,让响应数据支持泛型就已经实现完毕了。
(完)
查看更多关于使用Typescript重构axios(十三)——让响应数据支持泛型的详细内容...