使用Typescript重构axios(十三)——让响应数据支持泛型
<h1 id="0-系列文章">0. 系列文章</h1><p>1.使用Typescript重构axios(一)——写在最前面<br>
2.使用Typescript重构axios(二)——项目起手,跑通流程<br>
3.使用Typescript重构axios(三)——实现基础功能:处理get请求url参数<br>
4.使用Typescript重构axios(四)——实现基础功能:处理post请求参数<br>
5.使用Typescript重构axios(五)——实现基础功能:处理请求的header<br>
6.使用Typescript重构axios(六)——实现基础功能:获取响应数据<br>
7.使用Typescript重构axios(七)——实现基础功能:处理响应header<br>
8.使用Typescript重构axios(八)——实现基础功能:处理响应data<br>
9.使用Typescript重构axios(九)——异常处理:基础版<br>
10.使用Typescript重构axios(十)——异常处理:增强版<br>
11.使用Typescript重构axios(十一)——接口扩展<br>
12.使用Typescript重构axios(十二)——增加参数<br>
13.使用Typescript重构axios(十三)——让响应数据支持泛型<br>
14.使用Typescript重构axios(十四)——实现拦截器<br>
15.使用Typescript重构axios(十五)——默认配置<br>
16.使用Typescript重构axios(十六)——请求和响应数据配置化<br>
17.使用Typescript重构axios(十七)——增加axios.create<br>
18.使用Typescript重构axios(十八)——请求取消功能:总体思路<br>
19.使用Typescript重构axios(十九)——请求取消功能:实现第二种使用方式<br>
20.使用Typescript重构axios(二十)——请求取消功能:实现第一种使用方式<br>
21.使用Typescript重构axios(二十一)——请求取消功能:添加axios.isCancel接口<br>
22.使用Typescript重构axios(二十二)——请求取消功能:收尾<br>
23.使用Typescript重构axios(二十三)——添加withCredentials属性<br>
24.使用Typescript重构axios(二十四)——防御XSRF攻击<br>
25.使用Typescript重构axios(二十五)——文件上传下载进度监控<br>
26.使用Typescript重构axios(二十六)——添加HTTP授权auth属性<br>
27.使用Typescript重构axios(二十七)——添加请求状态码合法性校验<br>
28.使用Typescript重构axios(二十八)——自定义序列化请求参数<br>
29.使用Typescript重构axios(二十九)——添加baseURL<br>
30.使用Typescript重构axios(三十)——添加axios.getUri方法<br>
31.使用Typescript重构axios(三十一)——添加axios.all和axios.spread方法<br>
32.使用Typescript重构axios(三十二)——写在最后面(总结)</p>
<p>项目源码请猛戳这里!!!</p>
<h1 id="1-前言">1. 前言</h1>
<p>在大型项目开发中,基于模块化开发的思想,我们往往不会把所有的请求操作直接写入逻辑内,而是将所有的请求按照需求不同分门别类的统一放在一个地方,例如博主在手头的项目开发中,会在项目目录下新建一个叫做<code>api</code>的文件夹,在该文件夹内根据业务模块的不同放置不同的请求文件,例如,关于用户增删改查的请求会在<code>api</code>文件夹内新建一个<code>user.ts</code>文件,然后将四个请求放入该文件;关于日志增删改查的请求会放入<code>log.ts</code>文件中,如下所示:</p>
<pre><code class="language-text">├── api
├── user.ts // 用户相关的请求
├── log.ts // 日志相关的请求
...
</code></pre>
<p>而在每个文件中,也会把每个请求抽离成单独函数导出,供需要的地方调用,如在<code>user.ts</code>中:</p>
<pre><code class="language-javascript">// 获取用户
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))
}
// ...
</code></pre>
<p>这样做的好处是,所有请求能够被集中统一的管理起来,如果日后有变动也可以快速的找到。</p>
<p>前言说了这么多,还是没有引入正题,其实博主是想说:当我们发出请求后,我们最关心的一个是请求是否成功,另外一个就是返回的响应数据是不是我们想要的,我们能否预先定义一个期望返回的数据类型接口,然后看返回的响应数据能否匹配预先定义的接口,就能够得知返回的数据是不是我们想要的?答案当然是可以的。</p>
<h1 id="2-需求分析">2. 需求分析</h1>
<p>我们之前给所有的请求响应都规定一个类型接口,我们规定,所有的请求返回的响应都应该包含以上几个部分,如下:</p>
<pre><code class="language-typescript">export interface AxiosResponse {
data: any; // 服务端返回的数据
status: number; // HTTP 状态码
statusText: string; // 状态消息
headers: any; // 响应头
config: AxiosRequestConfig; // 请求配置对象
request: any; // 请求的 XMLHttpRequest 对象实例
}
</code></pre>
<p>但是仅仅是这样还不够,我们还希望能够细化到返回的数据<code>data</code>上,例如,当我们获取用户时发出<code>getUser</code>请求时,我们希望返回的数据<code>data</code>应该是<code>{name:'难凉热血',age:'18'}</code>这样的,由于每个请求期望返回的<code>data</code>不尽相同,那么我们就应该在发出请求时带上我们想要的<code>data</code>类型接口,当数据<code>data</code>返回时去匹配我们所携带的接口看是否匹配得上,进而确保是我们想要的数据。</p>
<p>那么,这就要求我们上面定义的<code>AxiosResponse</code>接收一个泛型参数,这个参数就是返回数据<code>data</code>参数,如下:</p>
<pre><code class="language-typescript">export interface AxiosResponse<T = any> {
data: T; // 服务端返回的数据
status: number; // HTTP 状态码
statusText: string; // 状态消息
headers: any; // 响应头
config: AxiosRequestConfig; // 请求配置对象
request: any; // 请求的 XMLHttpRequest 对象实例
}
</code></pre>
<p>这里我们给 <code>AxiosResponse</code> 接口添加了泛型参数 <code>T</code>,<code>T=any</code> 表示泛型的类型参数默认值为 <code>any</code>。</p>
<p>有了这个泛型参数以后,我们在发送请求时就可以指定返回的<code>data</code>的类型了,如下:</p>
<pre><code class="language-typescript">// 获取用户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>()
}
</code></pre>
<h1 id="3-接口添加泛型参数">3. 接口添加泛型参数</h1>
<p>接下来,我们就为之前定义好的所有接口都加上泛型参数。</p>
<pre><code class="language-typescript">// 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>;
}
</code></pre>
<p>我们为 <code>AxiosPromise</code>、<code>Axios</code> 以及 <code>AxiosInstance</code> 接口都加上了泛型参数。我们可以看到这些请求的返回类型都变成了 <code>AxiosPromise<T></code>,也就是 <code>Promise<AxiosResponse<T>></code>,这样我们就可以从响应中拿到了类型 <code>T</code> 了。</p>
<p>OK,响应数据的泛型参数就已经添加好了,接下里,就可以编写<code>demo</code>来测试一下效果。</p>
<h1 id="4-demo编写">4. demo编写</h1>
<p>在 <code>examples</code> 目录下创建 <code>addGenericityToAxiosResponse</code>目录,在 <code>addGenericityToAxiosResponse</code>目录下创建 <code>index.html</code>:</p>
<pre><code class="language-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>
</code></pre>
<p>接着再创建 <code>app.ts</code> 作为入口文件:</p>
<pre><code class="language-typescript">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();
</code></pre>
<p>我们看到,在编写代码的时候,<code>TypeScript</code>已经可以帮我们推断出<code>user</code>中我们预先定义好的想要的数据了。</p>
<p><img src="https://img2018.cnblogs.com/blog/1460995/201908/1460995-20190804194236650-183936991.gif" alt="" loading="lazy"></p>
<p>接着在 <code>server/server.js</code> 添加新的接口路由:</p>
<pre><code class="language-javascript">// 响应支持泛型
router.get("/api/getuser", function(req, res) {
res.json({
msg: "hello world",
data: { name: "难凉热血", age: 18 }
});
});
</code></pre>
<p>最后在根目录下的<code>index.html</code>中加上启动该<code>demo</code>的入口:</p>
<pre><code class="language-html"><li><a href="examples/addGenericityToAxiosResponse">addGenericityToAxiosResponse</a></li>
</code></pre>
<p>OK,我们在命令行中执行:</p>
<pre><code># 同时开启客户端和服务端
npm run server | npm start
</code></pre>
<p>接着我们打开 <code>chrome</code> 浏览器,访问 http://localhost:8000/ 即可访问我们的 <code>demo</code> 了,我们点击 <code>addGenericityToAxiosResponse</code>,通过<code>F12</code>的 <code>network</code> 部分我们可以看到请求已正常发出:<br>
<img src="https://img2018.cnblogs.com/blog/1460995/201908/1460995-20190804194251909-1375964179.png" alt="" loading="lazy"></p>
<p>OK,让响应数据支持泛型就已经实现完毕了。</p>
<p>(完)</p>
</div>
<div id="MySignature" role="contentinfo">
<br>
<br>
<br>
<h1 style="color: red">免责声明</h1>
<ul>
<li>本博客所有文章仅用于学习、研究和交流目的,欢迎非商业性质转载。</li>
<li>博主在此发文(包括但不限于汉字、拼音、拉丁字母)均为随意敲击键盘所出,用于检验本人电脑键盘录入、屏幕显示的机械、光电性能,并不代表本人局部或全部同意、支持或者反对观点。如需要详查请直接与键盘生产厂商法人代表联系。挖井挑水无水表,不会网购无快递。</li>
<li>博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。</li>
<li>博主是利用读书、参考、引用、抄袭、复制和粘贴等多种方式打造成自己的文章,请原谅博主成为一个无耻的文档搬运工!</li>
</ul><br><br>
来源:https://www.cnblogs.com/wangjiachen666/p/11299363.html
頁:
[1]