欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue3?TypeScript?實(shí)現(xiàn)useRequest詳情

 更新時(shí)間:2022年05月26日 15:57:38   作者:??woodenF????  
本文介紹了Vue3?TypeScript實(shí)現(xiàn)useRequest詳情,useRequest可能是目前社區(qū)中最強(qiáng)大,最接地氣的請(qǐng)求類?Hooks了??梢愿采w99%的網(wǎng)絡(luò)請(qǐng)求場(chǎng)景,無論是讀還是寫,無論是普通請(qǐng)求還是分頁(yè)請(qǐng)求,無論是緩存還是防抖節(jié)流,通通都能支持,關(guān)于其介紹需要的小伙伴可以參考一下

前言:

自從 Vue3 更新之后,算是投入了比較大的精力寫了一個(gè)較為完善的Vue3.2 + Vite2 + Pinia + Naive UI的B端模版,在做到網(wǎng)絡(luò)請(qǐng)求這一塊的時(shí)候,最初使用的是VueRequestuseRequest,但是因?yàn)?code>VueRequest的useRequestcancel關(guān)閉請(qǐng)求并不是真正的關(guān)閉,對(duì)我個(gè)人來說,還是比較介意,于是在參考aHooksVueRequest的源碼之后,差不多弄了一個(gè)簡(jiǎn)易的useRequest,使用體驗(yàn)還算ok,但是因?yàn)閭€(gè)人能力以及公司業(yè)務(wù)的問題,我的版本只支持axios,不支持fetch,算是作為公司私有的庫(kù)使用,沒有考慮功能的大而全,也只按VueRequest的官網(wǎng),實(shí)現(xiàn)了一部分我認(rèn)為最重要的功能。

寫的比較混亂,中間是一部分思考,可以直接拖到最后看實(shí)現(xiàn),再回來看一下我為什么選擇這么做,歡迎討論。

效果展示

一個(gè)基礎(chǔ)的useRequest示例,支持發(fā)起請(qǐng)求 取消請(qǐng)求 請(qǐng)求成功信息 成功回調(diào) 錯(cuò)誤捕獲

queryKey示例,單個(gè)useRequest管理多個(gè)相同請(qǐng)求。

其余還是依賴更新 重復(fù)請(qǐng)求關(guān)閉 防抖 節(jié)流等功能

Axios

既然咱們使用TypeScriptaxios,為了使axios能滿足咱們的使用需求以及配合TypeScript的編寫時(shí)使用體驗(yàn),咱們對(duì)axios進(jìn)行一個(gè)簡(jiǎn)單的封裝。

interface

// /src/hooks/useRequest/types.ts
import { AxiosResponse, Canceler } from 'axios';
import { Ref } from 'vue';
// 后臺(tái)返回的數(shù)據(jù)類型
export interface Response<T> {
    code: number;
    data: T;
    msg: string;
}
// 為了使用方便,對(duì) AxiosResponse 默認(rèn)添加我們公用的 Response 類型
export type AppAxiosResponse<T = any> = AxiosResponse<Response<T>>;

// 為了 useRequest 使用封裝的類型
export interface RequestResponse<T> {
    instance: Promise<AppAxiosResponse<T>>;
    cancel: Ref<Canceler | undefined>;
}

axios的簡(jiǎn)單封裝

因?yàn)樵蹅儸F(xiàn)在沒有接入業(yè)務(wù),所以axios只需要簡(jiǎn)單的封裝能支持咱們useRequest的需求即可。

import { ref } from 'vue';
import { AppAxiosResponse, RequestResponse } from './types';
import axios, { AxiosRequestConfig, Canceler } from 'axios';
const instance = axios.create({
  timeout: 30 * 1000,
  baseURL: '/api'
});
export function request<T>(config: AxiosRequestConfig): RequestResponse<T> {
  const cancel = ref<Canceler>();
  return {
    instance: instance({
      ...config,
      cancelToken: new axios.CancelToken((c) => {
        cancel.value = c;
      })
    }),
    cancel
  };
}

例:

import { IUser } from '@/interface/User';
export function getUserInfo(id: number) {
  return request<IUser>({
    url: '/getUserInfo',
    method: 'get',
    params: {
      id
    }
  });
}

需要注意的是,示例中的錯(cuò)誤信息經(jīng)過了統(tǒng)一性的封裝,如果希望錯(cuò)誤有一致性的表現(xiàn),可以封裝一個(gè)類型接收錯(cuò)誤,建議與后臺(tái)返回的數(shù)據(jù)結(jié)構(gòu)一致。

現(xiàn)在,咱們使用這個(gè)request函數(shù),傳入對(duì)應(yīng)的泛型,就可以享受到對(duì)應(yīng)的類型提示

useRequest

如何使用

想要設(shè)計(jì)useRequest,那現(xiàn)在思考一下,什么樣的useRequest使用起來,能讓我們感到快樂,拿上面的基礎(chǔ)示例queryKey示例來看,大家可以參考一下VueRequest或者aHooks的用法,我是看了他們的用法來構(gòu)思我的設(shè)計(jì)的。

比如一個(gè)普通的請(qǐng)求,我希望簡(jiǎn)單的使用data、loadingerr等來接受數(shù)據(jù),比如:

const { run, data, loading, cancel, err } = useRequest(getUserInfo, {
    manual: true
})

那 useRequest 的簡(jiǎn)單模型好像是這樣的

export function useRequest(service, options) {
    return {
        data,
        run,
        loading,
        cancel,
        err
    }
}

傳入一個(gè)請(qǐng)求函數(shù)配置信息,請(qǐng)求交由useRequest內(nèi)部接管,最后將data loading等信息返回即可。

那加上queryKey

const { run, querise } = useRequest(getUserInfo, {
    manual: true,
    queryKey: (id) => String(id) 
})

似乎還要返回一個(gè)querise,于是變成了

export function useRequest(service, options) {
    return {
        data,
        run,
        loading,
        cancel,
        err,
        querise
    }
}

對(duì)應(yīng)的querise[key]選項(xiàng),還要額外維護(hù)data loading等屬性,這樣對(duì)于useRequest內(nèi)部來說是不是太割裂了呢,大家可以嘗試一下,因?yàn)槲揖褪且婚_始做簡(jiǎn)單版本之后再來考慮queryKey功能的,代碼是十分難看的。

添加泛型支持

上面的偽代碼我們都沒有添加泛型支持,那我們需要添加哪些泛型,上面request的例子其實(shí)比較明顯了

import { IUser } from '@/interface/User';
export function getUserInfo(id: number) {
  return request<IUser>({
    url: '/getUserInfo',
    method: 'get',
    params: {
      id
    }
  });
}

對(duì)于id,作為請(qǐng)求參數(shù),我們每一個(gè)請(qǐng)求都不確定,這里肯定是需要一個(gè)泛型的,IUser作為返回類型的泛型,需要被useRequest正確識(shí)別,必然也是需要一個(gè)泛型的。

其中,請(qǐng)求參數(shù)的泛型,為了使用的方便,我們定義其extends any[],必須是一個(gè)數(shù)組,使用...args的形式傳入到requestinstance中執(zhí)行。

service的類型需要與request類型保持一致, options的類型按需要實(shí)現(xiàn)的功能參數(shù)添加,于是,我們得到了如下一個(gè)useRequest

// /src/hooks/useRequest/types.ts
export type Service<T, P extends any[]> = (...args: P) => RequestResponse<T>;
// 可按對(duì)應(yīng)的配置項(xiàng)需求擴(kuò)展
export interface Options<T, P extnds any> {
  // 是否手動(dòng)發(fā)起請(qǐng)求
  manual?: boolean;
  // 當(dāng) manual 為false時(shí),自動(dòng)執(zhí)行的默認(rèn)參數(shù)
  defaultParams?: P;
  // 依賴項(xiàng)更新
  refreshDeps?: WatchSource<any>[];
  refreshDepsParams?: ComputedRef<P>;
  // 是否關(guān)閉重復(fù)請(qǐng)求,當(dāng)queryKey存在時(shí),該字段無效
  repeatCancel?: boolean;
  // 并發(fā)請(qǐng)求
  queryKey?: (...args: P) => string;
  // 成功回調(diào)
  onSuccess?: (response: AxiosResponse<Response<T>>, params: P) => void;
  // 失敗回調(diào)
  onError?: (err: ErrorData, params: P) => void;
}
// /src/hooks/useRequest/index.ts
export function useRequest<T, P extends any[]>(
    service: Service<T, P>,
    options: Options<T, P> = {}
){
    return {
        data, // data 類型為T
        run,
        loading,
        cancel,
        err,
        querise
    }
}

queryKey的問題

上面我們提到了,queryKey請(qǐng)求普通請(qǐng)求如果單獨(dú)維護(hù),不僅割裂,而且代碼還很混亂,那有沒有什么辦法來解決這個(gè)問題呢,用js的思想來看這個(gè)問題,假設(shè)我現(xiàn)在有一個(gè)對(duì)象querise,我需要將不同請(qǐng)求參數(shù)的請(qǐng)求相關(guān)數(shù)據(jù)維護(hù)到querise中,比如run(1),那么querise應(yīng)該為

const querise = {
  1: {
      data: null,
      loading: false
      ...
  }
}

這是在queryKey的情況下,那沒有queryKey呢?很簡(jiǎn)單,維護(hù)到default對(duì)象唄,即

const querise = {
  default: {
      data: null,
      loading: false
      ...
  }
}

為了確保默認(rèn)key值的唯一性,我們引入Symbol,即

const defaultQuerise = Symbol('default');
const querise = {
  [defaultQuerise]: {
      data: null,
      loading: false
      ...
  }
}

因?yàn)槲覀儠?huì)使用reactive包裹querise,所以想要滿足非queryKey請(qǐng)求時(shí),使用默認(rèn)導(dǎo)出的data loading err等數(shù)據(jù),只需要

return {
    run,
    querise,
    ...toRefs(querise[defaulrQuerise])
}

好了,需要討論的問題完了,我們來寫代碼

完整代碼

// /src/hooks/useRequest/types.ts
import { Canceler, AxiosResponse } from 'axios';
import { ComputedRef, WatchSource, Ref } from 'vue';
export interface Response<T> {
  code: number;
  data: T;
  msg: string;
}
export type AppAxiosResponse<T = any> = AxiosResponse<Response<T>>;
export interface RequestResponse<T>{
  instance: Promise<AppAxiosResponse<T>>;
  cancel: Ref<Canceler | undefined>
}
export type Service<T, P extends any[]> = (...args: P) => RequestResponse<T>;
export interface Options<T, P extends any[]> {
  // 是否手動(dòng)發(fā)起請(qǐng)求
  manual?: boolean;
  // 當(dāng) manual 為false時(shí),自動(dòng)執(zhí)行的默認(rèn)參數(shù)
  defaultParams?: P;
  // 依賴項(xiàng)更新
  refreshDeps?: WatchSource<any>[];
  refreshDepsParams?: ComputedRef<P>;
  // 是否關(guān)閉重復(fù)請(qǐng)求,當(dāng)queryKey存在時(shí),該字段無效
  repeatCancel?: boolean;
  // 重試次數(shù)
  retryCount?: number;
  // 重試間隔時(shí)間
  retryInterval?: number;
  // 并發(fā)請(qǐng)求
  queryKey?: (...args: P) => string;
  // 成功回調(diào)
  onSuccess?: (response: AxiosResponse<Response<T>>, params: P) => void;

  // 失敗回調(diào)
  onError?: (err: ErrorData, params: P) => void;
}
export interface IRequestResult<T> {
  data: T | null;
  loading: boolean;
  cancel: Canceler;
  err?: ErrorData;
}
export interface ErrorData<T = any> {
  code: number | string;
  data: T;
  msg: string;
}
// /src/hooks/useRequest/axios.ts
import { ref } from 'vue';
import { AppAxiosResponse, RequestResponse } from './types';
import axios, { AxiosRequestConfig, Canceler } from 'axios';
const instance = axios.create({
  timeout: 30 * 1000,
  baseURL: '/api'
});

instance.interceptors.request.use(undefined, (err) => {
  console.log('request-error', err);
});

instance.interceptors.response.use((res: AppAxiosResponse) => {
  if(res.data.code !== 200) {
    return Promise.reject(res.data);
  }
  return res;
}, (err) => {
  if(axios.isCancel(err)) {
    return Promise.reject({
      code: 10000,
      msg: 'Cancel',
      data: null
    });
  }
  if(err.code === 'ECONNABORTED') {
    return Promise.reject({
      code: 10001,
      msg: '超時(shí)',
      data: null
    });
  }
  console.log('response-error', err.toJSON());
  return Promise.reject(err);
});
export function request<T>(config: AxiosRequestConfig): RequestResponse<T> {
  const cancel = ref<Canceler>();
  return {
    instance: instance({
      ...config,
      cancelToken: new axios.CancelToken((c) => {
        cancel.value = c;
      })
    }),
    cancel
  };
}
import { isFunction } from 'lodash';
import { reactive, toRefs, watch } from 'vue';
import { IRequestResult, Options, Service, ErrorData } from './types';
const defaultQuerise = Symbol('default');
export function useRequest<T, P extends any[]>(
  service: Service<T, P>,
  options: Options<T, P> = {}
) {
  const {
    manual = false,
    defaultParams = [] as unknown as P,
    repeatCancel = false,
    refreshDeps = null,
    refreshDepsParams = null,
    queryKey = null
  } = options;

  const querise = reactive<Record<string | symbol, IRequestResult<T>>>({
    [defaultQuerise]: {
      data: null,
      loading: false,
      cancel: () => null,
      err: undefined
    }
  });
  const serviceFn = async (...args: P) => {
    const key = queryKey ? queryKey(...args) : defaultQuerise;
    if (!querise[key]) {
      querise[key] = {} as any;
    }
    if (!queryKey && repeatCancel) {
      querise[key].cancel();
    }
    querise[key].loading = true;
    const { instance, cancel } = service(...args);
    querise[key].cancel = cancel as any;
    instance
      .then((res) => {
        querise[key].data = res.data.data;
        querise[key].err = undefined;
        if (isFunction(options.onSuccess)) {
          options.onSuccess(res, args);
        }
      })
      .catch((err: ErrorData) => {
        querise[key].err = err;
        if (isFunction(options.onError)) {
          options.onError(err, args);
        }
      })
      .finally(() => {
        querise[key].loading = false;
      });
  };

  const run = serviceFn;
  // 依賴更新
  if (refreshDeps) {
    watch(
      refreshDeps,
      () => {
        run(...(refreshDepsParams?.value || ([] as unknown as P)));
      },
      { deep: true }
    );
  }

  if (!manual) {
    run(...defaultParams);
  }

  return {
    run,
    querise,
    ...toRefs(querise[defaultQuerise])
  };
}

需要防抖 節(jié)流 錯(cuò)誤重試等功能,僅需要擴(kuò)展Options類型,在useRequest中添加對(duì)應(yīng)的邏輯即可,比如使用lodash包裹run函數(shù),這里只是將最基本的功能實(shí)現(xiàn)搞定了,一部分小問題以及擴(kuò)展性的東西沒有過分糾結(jié)。

結(jié)語(yǔ)

到此這篇關(guān)于Vue3 TypeScript 實(shí)現(xiàn)useRequest詳情的文章就介紹到這了,更多相關(guān)TypeScript實(shí)現(xiàn) useRequest內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論