ts對axios的簡單封裝

在前端項目中肠牲,和后臺交互獲取數(shù)據(jù)這塊竹观,我們通常使用的是axios庫,axios是一個基于 promise 的HTTP庫支救,可運行在 client 端和 server 端。
雖然axios的使用已經(jīng)很方便了拷淘,但是在實際項目中各墨,為了接口的規(guī)則一致,來創(chuàng)建一個統(tǒng)一管理的全局方法以達到簡化操作的目的启涯。

1.安裝axios

yarn add axios -D
// 或者
npm i axios -D

2.定義需要的接口

因為使用了typescript贬堵,所以需要先定義好一些接口,以便在后續(xù)的代碼使用


export type Method = 'GET' | 'POST' | 'PUT' | 'DELETE'
export type ResponseType = 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'

export interface AxiosRequest {
    baseURL?: string;
    url: string;
    data?: any;
    params?: any;
    method?: Method;
    headers?: any;
    timeout?: number;
    responseType?: ResponseType;
}

export interface AxiosResponse {
    data: any;
    headers: any;
    request?: any;
    status: number;
    statusText: string;
    config: AxiosRequest;
}

export interface CustomResponse {
    readonly status: boolean;
    readonly message: string;
    data: any;
    origin?: any;
}

export interface GetDemo {
    id: number;
    str: string;
}

export interface PostDemo {
    id: number;
    list: Array<{
        id: number;
        version: number;
    }>;
}

3.創(chuàng)建axios實例结洼,并加入攔截器

根據(jù)指定配置創(chuàng)建一個實例黎做,在這里對重復(fù)的請求做 cancel 處理,還可以對失敗的請求重新發(fā)起請求操作

import axios, { AxiosRequestConfig, Method } from 'axios';
import { Loading } from 'element-ui';
import { ElLoadingComponent } from 'element-ui/types/loading';

// 定義接口
interface PendingType {
    url?: string;
    method?: Method;
    params: any;
    data: any;
    cancel: Function;
}

// 取消重復(fù)請求
const pending: Array<PendingType> = [];
const CancelToken = axios.CancelToken;
// axios 實例
const instance = axios.create({
    timeout: 10000,
    responseType: 'json'
});
let loadingInstance: ElLoadingComponent;

// 移除重復(fù)請求
const removePending = (config: AxiosRequestConfig) => {
    for (const key in pending) {
        const item: number = +key;
        const list: PendingType = pending[key];
        // 當前請求在數(shù)組中存在時執(zhí)行函數(shù)體
        if (list.url === config.url && list.method === config.method && JSON.stringify(list.params) === JSON.stringify(config.params) && JSON.stringify(list.data) === JSON.stringify(config.data)) {
            // 執(zhí)行取消操作
            list.cancel('操作太頻繁松忍,請稍后再試');
            // 從數(shù)組中移除記錄
            pending.splice(item, 1);
        }
    }
};

// 添加請求攔截器
instance.interceptors.request.use(
    request => {
        loadingInstance = Loading.service({
            text: '加載中',
            background: 'rgba(0, 0, 0, 0.3)'
        });

        removePending(request);
        request.cancelToken = new CancelToken((c) => {
            pending.push({ url: request.url, method: request.method, params: request.params, data: request.data, cancel: c });
        });
        return request;
    },
    error => {
        return Promise.reject(error);
    }
);

// 添加響應(yīng)攔截器
instance.interceptors.response.use(
    response => {
        loadingInstance.close();
        removePending(response.config);

        const errorCode = response?.data?.errorCode;
        switch (errorCode) {
            case '401':
                // 根據(jù)errorCode蒸殿,對業(yè)務(wù)做異常處理(和后端約定)
                break;
            default:
                break;
        }

        return response;
    },
    error => {
        loadingInstance.close();
        const response = error.response;

        // 根據(jù)返回的http狀態(tài)碼做不同的處理
        switch (response?.status) {
            case 401:
                // token失效
                break;
            case 403:
                // 沒有權(quán)限
                break;
            case 500:
                // 服務(wù)端錯誤
                break;
            case 503:
                // 服務(wù)端錯誤
                break;
            default:
                break;
        }

        // 超時重新請求
        const config = error.config;
        // 全局的請求次數(shù),請求的間隙
        const [RETRY_COUNT, RETRY_DELAY] = [3, 1000];

        if (config && RETRY_COUNT) {
            // 設(shè)置用于跟蹤重試計數(shù)的變量
            config.__retryCount = config.__retryCount || 0;
            // 檢查是否已經(jīng)把重試的總數(shù)用完
            if (config.__retryCount >= RETRY_COUNT) {
                return Promise.reject(response || { message: error.message });
            }
            // 增加重試計數(shù)
            config.__retryCount++;
            // 創(chuàng)造新的Promise來處理指數(shù)后退
            const backoff = new Promise((resolve) => {
                setTimeout(() => {
                    resolve();
                }, RETRY_DELAY || 1);
            });
            // instance重試請求的Promise
            return backoff.then(() => {
                return instance(config);
            });
        }

        // eslint-disable-next-line
        return Promise.reject(response || {message: error.message});
    }
);

export default instance;

4.配置api字典表

制定一個url的字典表,利于后期的更新維護

/**
 * API URL Dict api 字典
 */

interface UrlDict {
    [key: string]: {
        [key: string]: string;
    };
}

const urlDict: UrlDict = {
    Basic: {
        GetDemo: 'admin/get',
        PostDemo: 'admin/post',
    }
};

const getUrl = (biz: string, UrlName: string): string => {
    try {
        const bizKeys = Object.keys(urlDict);
        if (bizKeys.indexOf(biz) < 0) {
            throw new Error('biz not in Dict');
        }
        let hostname = urlDict[biz][UrlName];
        if (!hostname) {
            throw new Error('url not in Dict');
        }
        if (hostname.substr(0, 1) === '/') {
            hostname = hostname.substr(1);
        }
        return hostname;
    } catch (err) {
        console.error(err);
        return '';
    }
};

export default getUrl;

5.封裝axios的基類

封裝一個基類,對子類暴露get宏所,post酥艳,putdelete方法楣铁。便于子類的繼承使用

/**
 * axios基礎(chǔ)構(gòu)建
 * @date 2019-12-24
 */

import Vue from 'vue';
import getUrl from './config';
import instance from './intercept';
import { AxiosRequest, CustomResponse } from './types';

class Abstract {
    // 外部傳入的baseUrl
    protected baseURL: string = process.env.VUE_APP_BaseURL;
    // 自定義header頭
    protected headers: object = {
        ContentType: 'application/json;charset=UTF-8'
    }

    private apiAxios({ baseURL = this.baseURL, headers = this.headers, method, url, data, params, responseType }: AxiosRequest): Promise<CustomResponse> {

        // url解析
        const _url = (url as string).split('.');
        url = getUrl(_url[0], _url[1]);

        return new Promise((resolve, reject) => {
            instance({
                baseURL,
                headers,
                method,
                url,
                params,
                data,
                responseType
            }).then((res) => {
                // 200:服務(wù)端業(yè)務(wù)處理正常結(jié)束
                if (res.status === 200) {
                    if (res.data.success) {
                        resolve({ status: true, message: 'success', data: res.data?.data, origin: res.data });
                    } else {
                        Vue.prototype.$message({ type: 'error', message: res.data?.errorMessage || (url + '請求失敗') });
                        resolve({ status: false, message: res.data?.errorMessage || (url + '請求失敗'), data: res.data?.data, origin: res.data });
                    }
                } else {
                    resolve({ status: false, message: res.data?.errorMessage || (url + '請求失敗'), data: null });
                }
            }).catch((err) => {
                const message = err?.data?.errorMessage || err?.message || (url + '請求失敗');
                Vue.prototype.$message({ type: 'error', message });
                // eslint-disable-next-line
                reject({ status: false, message, data: null});
            });
        });
    }

    /**
     * GET類型的網(wǎng)絡(luò)請求
     */
    protected getReq({ baseURL, headers, url, data, params, responseType }: AxiosRequest) {
        return this.apiAxios({ baseURL, headers, method: 'GET', url, data, params, responseType });
    }

    /**
     * POST類型的網(wǎng)絡(luò)請求
     */
    protected postReq({ baseURL, headers, url, data, params, responseType }: AxiosRequest) {
        return this.apiAxios({ baseURL, headers, method: 'POST', url, data, params, responseType });
    }

    /**
     * PUT類型的網(wǎng)絡(luò)請求
     */
    protected putReq({ baseURL, headers, url, data, params, responseType }: AxiosRequest) {
        return this.apiAxios({ baseURL, headers, method: 'PUT', url, data, params, responseType });
    }

    /**
     * DELETE類型的網(wǎng)絡(luò)請求
     */
    protected deleteReq({ baseURL, headers, url, data, params, responseType }: AxiosRequest) {
        return this.apiAxios({ baseURL, headers, method: 'DELETE', url, data, params, responseType });
    }
}

export default Abstract;

6.定義具體的業(yè)務(wù)請求

/**
 * 基礎(chǔ)數(shù)據(jù) API 集合類
 * 集成Abstract
 * @date 2020-1-14
 */
import Abstract from '../abstract';
import { GetDemo, PostDemo } from './types';

class Basic extends Abstract {
    
    /**
     * get示例
     */
    getDemo(params: GetDemo) {
        return this.getReq({ url: 'Basic.GetDemo', params });
    }
    
    /**
     * post示例
     */
    postDemo(data: PostDemo) {
        return this.postReq({ url: 'Basic.PostDemo', data });
    }
    
}

// 單列模式返回對象
let instance;
export default (() => {
    if (instance) return instance;
    instance = new Basic();
    return instance;
})();

以上就是我對axios的封裝玖雁,有啥不完善的地方,歡迎大家指正批評
附上 github 的工程地址:vue+ts盖腕,順手給樓主點個 star 吧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赫冬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子溃列,更是在濱河造成了極大的恐慌劲厌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件听隐,死亡現(xiàn)場離奇詭異补鼻,居然都是意外死亡,警方通過查閱死者的電腦和手機雅任,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門风范,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人沪么,你說我怎么就攤上這事硼婿。” “怎么了禽车?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵寇漫,是天一觀的道長。 經(jīng)常有香客問我殉摔,道長州胳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任逸月,我火速辦了婚禮栓撞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘碗硬。我一直安慰自己腐缤,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布肛响。 她就那樣靜靜地躺著岭粤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪特笋。 梳的紋絲不亂的頭發(fā)上剃浇,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天巾兆,我揣著相機與錄音,去河邊找鬼虎囚。 笑死角塑,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的淘讥。 我是一名探鬼主播圃伶,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蒲列!你這毒婦竟也來了窒朋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蝗岖,失蹤者是張志新(化名)和其女友劉穎侥猩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抵赢,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡欺劳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了铅鲤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片划提。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖邢享,靈堂內(nèi)的尸體忽然破棺而出鹏往,到底是詐尸還是另有隱情,我是刑警寧澤驼仪,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站袜漩,受9級特大地震影響绪爸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宙攻,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一奠货、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧座掘,春花似錦递惋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至形真,卻和暖如春杉编,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工邓馒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嘶朱,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓光酣,卻偏偏與公主長得像疏遏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子救军,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355