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
接口添加了泛型參數 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,讓響應數據支持泛型就已經實現完畢了。
(完)