使用Typescript重構axios(十三)——讓響應數據支持泛型


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. 前言

在大型項目開發中,基於模塊化開發的思想,我們往往不會把所有的請求操作直接寫入邏輯內,而是將所有的請求按照需求不同分門別類的統一放在一個地方,例如博主在手頭的項目開發中,會在項目目錄下新建一個叫做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 接口添加了泛型參數 TT=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>;
}

我們為 AxiosPromiseAxios 以及 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,通過F12network 部分我們可以看到請求已正常發出:

OK,讓響應數據支持泛型就已經實現完畢了。

(完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM