設計一個vue3的請求實體工廠
描述
PS: 這裡的方法是基於Vue的class寫法的,對於setup寫法不適用。
主要功能是建立一個具備一個請求完整封裝的例項,可以便捷的請求,取消請求,獲取資料和請求狀態等功能
下面是通過typescript和Vue實現,結合typescript的宣告,這個generateRequest方法才會得到昇華
實現
實現分下面3個部分
1.構建一個基礎請求方法
2.建立具體請求的方法
3.generateRequest對請求的封裝
構建一個基礎請求方法
這裡沒啥說的,直接上axios
做一個簡單的封裝
request.ts
import axios from 'axios'
const option = {...}
export request = axios.create(option)
建立具體請求的方法
下面是對請求的宣告檔案
xxx.d.ts
// 對請求返回的請求體宣告
export class Result<T0 = any> {
/** code */
code?:number;
/** data */
data?:T0;
/** error_code */
error_code?:string;
/** error_message */
error_message?:string;
/** success */
success?:boolean;
}
// 文章介面返回data宣告
export interface ArticleData {
id:number;
title:string;
cover_url:string;
content:string;
read_count:string;
abstract:string;
time:number;
}
下面是請求的定義
api.ts
import { request } from './request'
import { AxiosRequestConfig } from 'axios'
import { Result, ArticleData } from './xxx'
/** 獲取文章 */
export const getArticle = (id:number, config?:AxiosRequestConfig) => request.get<Result<ArticleData>>(`/api/article?id=${id}`)
generateRequest對請求的封裝
這裡主要用到的知識點有:
1.由對映型別進行推斷(對引數進行拆包)
2.對上下文的理解
3.vue資料響應式機制
下面是一個基礎實現,還可以繼續封裝對業務場景有用的方法或屬性
功能
- 具備響應式屬性:data(返回的資料)loading、isError、params(請求的引數)
- 可以傳入資料處理的方法format
- 設定data初始值initData
- 取消請求的cancel方法
- 發起請求的run方法
utils.ts
import axios, { AxiosPromise, AxiosRequestConfig } from 'axios';
interface GenerateRequestHook<T, K> {
data:T | undefined;
run:(params?:K) => AxiosPromise<T>;
loading:boolean;
isError:boolean;
uid:number;
cancel:((msg?:string) => void);
params:K | undefined;
}
interface Config<L> {
config?:AxiosRequestConfig;
format?:(data:any) => any;
initData?:L;
}
let _uid = 0;
/**
* @description 返回請求的封裝實體的request,僅可以在class寫法的組建內使用
* @template T
* @template K
* @param {(params?:K, config?:AxiosRequestConfig) => AxiosPromise<T>} reqFunc
* @param {K} [params]
* @param {Config<T>} [config]
* @returns {GenerateRequestHook<T>}
*/
export const generateRequest = <T, K>(reqFunc:(params?:K, config?:AxiosRequestConfig) => AxiosPromise<T>, params?:K, config?:Config<T>):GenerateRequestHook<T, K> => {
const uid = _uid++;
const source = axios.CancelToken.source();
return {
run: async function(_params?:K) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self:GenerateRequestHook<T, K> = this;
if (!self || uid !== self.uid) {
throw Error('[generateRequest] The context exception');
}
try {
self.isError = false;
self.loading = true;
if (_params) {
self.params = _params;
}
const res = await reqFunc(_params || params, {
cancelToken: source.token,
...config?.config,
});
// 格式化返回資料
if (config?.format) {
config.format(res.data);
}
self.data = res.data;
return res;
} catch (error) {
self.data = config?.initData;
self.isError = true;
return Promise.reject(error);
} finally {
self.loading = false;
}
},
data: config?.initData,
loading: false,
isError: false,
uid,
cancel: source.cancel,
params,
};
};
使用demo
<template>
<div class="article_info">
</div>
</template>
<script lang="ts">
import { Vue, Options } from 'vue-property-decorator';
import { generateRequest } from './utils';
import { getArticle } from './api';
@Options({})
export default class extends Vue {
articleReq = generateRequest(getArticle)
created() {
this.articleReq.run(1).then((res) => {
document.title = this.title;
}).catch((err) => {
console.error(err.message);
});
}
}
</script>
上面的this.articleReq會包含的屬性,我們可以直接使用loading
、data
、isError,
而且這些屬性都是具備響應式的,而且data屬性是根據傳入getArticle
推斷出資料型別,十分方便。可以通過cancel方法取消請求,run可以發起請求。
上面我的run方法內有這麼一段判斷,因為run方法內部是用了this來進行資料更新,讓屬性具備響應式。不過相對的限制就是不能修改this.articleReq.run呼叫時的上下文。這樣會導致獲取屬性異常。不過這個限制基本沒什麼影響,好處是大於壞處的。所以我給每個請求分配一個uid,用於判斷請求run方法的上下文是否一致。
if (!self || uid !== self.uid) {
throw Error('[generateRequest] The context exception');
}
結語
上面generateRequest
建立了一個具備響應式的請求例項,實現了從請求引數、請求資料都具備宣告,且可以直接在元件內使用具備響應式的屬性。目前來說還是挺方便的。