<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Vue3 TypeScript 實現(xiàn)一個 useRequest

          共 8896字,需瀏覽 18分鐘

           ·

          2022-05-31 23:24

          大廠技術??高級前端??Node進階

          點擊上方?程序員成長指北,關注公眾號

          回復1,加入高級Node交流群

          起因

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

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

          效果展示

          一個基礎的useRequest示例,支持發(fā)起請求 取消請求 請求成功信息 成功回調(diào) 錯誤捕獲

          queryKey示例,單個useRequest管理多個相同請求。

          其余還是依賴更新 重復請求關閉 防抖 節(jié)流等功能

          Axios

          既然咱們使用TypeScriptaxios,為了使axios能滿足咱們的使用需求以及配合TypeScript的編寫時使用體驗,咱們對axios進行一個簡單的封裝。

          interface

          //?/src/hooks/useRequest/types.ts
          import?{?AxiosResponse,?Canceler?}?from?'axios';
          import?{?Ref?}?from?'vue';

          //?后臺返回的數(shù)據(jù)類型
          export?interface?Response?{
          ????code:?number;
          ????data:?T;
          ????msg:?string;
          }

          //?為了使用方便,對?AxiosResponse?默認添加我們公用的?Response?類型
          export?type?AppAxiosResponseany>?=?AxiosResponse>;

          //?為了?useRequest?使用封裝的類型
          export?interface?RequestResponse?{
          ????instance:?Promise>;
          ????cancel:?Refundefined>;
          }

          復制代碼

          axios 的簡單封裝

          因為咱們現(xiàn)在沒有接入業(yè)務,所以axios只需要簡單的封裝能支持咱們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();
          ??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({
          ????url:?'/getUserInfo',
          ????method:?'get',
          ????params:?{
          ??????id
          ????}
          ??});
          }
          復制代碼

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

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

          useRequest

          如何使用

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

          比如一個普通的請求,我希望簡單的使用dataloadingerr等來接受數(shù)據(jù),比如

          const?{?run,?data,?loading,?cancel,?err?}?=?useRequest(getUserInfo,?{
          ????manual:?true
          })
          復制代碼

          useRequest 的簡單模型好像是這樣的

          export?function?useRequest(service,?options)?{
          ????return?{
          ????????data,
          ????????run,
          ????????loading,
          ????????cancel,
          ????????err
          ????}
          }
          復制代碼

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

          那加上queryKey

          const?{?run,?querise?}?=?useRequest(getUserInfo,?{
          ????manual:?true,
          ????queryKey:?(id)?=>?String(id)
          })
          復制代碼

          似乎還要返回一個querise,于是變成了

          export?function?useRequest(service,?options)?{
          ????return?{
          ????????data,
          ????????run,
          ????????loading,
          ????????cancel,
          ????????err,
          ????????querise
          ????}
          }
          復制代碼

          對應的querise[key]選項,還要額外維護data loading等屬性,這樣對于useRequest內(nèi)部來說是不是太割裂了呢,大家可以嘗試一下,因為我就是一開始做簡單版本之后再來考慮queryKey功能的,代碼是十分難看的。

          添加泛型支持

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

          import?{?IUser?}?from?'@/interface/User';

          export?function?getUserInfo(id:?number)?{
          ??return?request({
          ????url:?'/getUserInfo',
          ????method:?'get',
          ????params:?{
          ??????id
          ????}
          ??});
          }
          復制代碼

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

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

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

          //?/src/hooks/useRequest/types.ts

          export?type?Serviceextends?any[]>?=?(...args:?P)?=>?RequestResponse;

          //?可按對應的配置項需求擴展
          export?interface?Options?{
          ??//?是否手動發(fā)起請求
          ??manual?:?boolean;

          ??//?當?manual?為false時,自動執(zhí)行的默認參數(shù)
          ??defaultParams?:?P;

          ??//?依賴項更新
          ??refreshDeps?:?WatchSource<any>[];
          ??refreshDepsParams?:?ComputedRef

          ;

          ??//?是否關閉重復請求,當queryKey存在時,該字段無效
          ??repeatCancel?:?boolean;

          ??//?并發(fā)請求
          ??queryKey?:?(...args:?P)?=>?string;

          ??//?成功回調(diào)
          ??onSuccess?:?(response:?AxiosResponse>,?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,
          ????options:?Options?=?{}
          )
          {
          ????return?{
          ????????data,?//?data?類型為T
          ????????run,
          ????????loading,
          ????????cancel,
          ????????err,
          ????????querise
          ????}
          }
          復制代碼

          queryKey 的問題

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

          const?querise?=?{
          ??1:?{
          ??????data:?null,
          ??????loading:?false
          ??????...
          ??}
          }
          復制代碼

          這是在queryKey的情況下,那沒有queryKey呢?很簡單,維護到default對象唄,即

          const?querise?=?{
          ??default:?{
          ??????data:?null,
          ??????loading:?false
          ??????...
          ??}
          }
          復制代碼

          為了確保默認key值的唯一性,我們引入Symbol,即

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

          因為我們會使用reactive包裹querise,所以想要滿足非queryKey請求時,使用默認導出的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?{
          ??code:?number;
          ??data:?T;
          ??msg:?string;
          }

          export?type?AppAxiosResponseany>?=?AxiosResponse>;

          export?interface?RequestResponse{
          ??instance:?Promise>;
          ??cancel:?Refundefined>
          }


          export?type?Serviceextends?any[]>?=?(...args:?P)?=>?RequestResponse;

          export?interface?Optionsextends?any[]>?{
          ??//?是否手動發(fā)起請求
          ??manual?:?boolean;

          ??//?當?manual?為false時,自動執(zhí)行的默認參數(shù)
          ??defaultParams?:?P;

          ??//?依賴項更新
          ??refreshDeps?:?WatchSource<any>[];
          ??refreshDepsParams?:?ComputedRef

          ;

          ??//?是否關閉重復請求,當queryKey存在時,該字段無效
          ??repeatCancel?:?boolean;

          ??//?重試次數(shù)
          ??retryCount?:?number;
          ??//?重試間隔時間
          ??retryInterval?:?number;

          ??//?并發(fā)請求
          ??queryKey?:?(...args:?P)?=>?string;

          ??//?成功回調(diào)
          ??onSuccess?:?(response:?AxiosResponse>,?params:?P)?=>?void;

          ??//?失敗回調(diào)
          ??onError?:?(err:?ErrorData,?params:?P)?=>?void;
          }

          export?interface?IRequestResult?{
          ??data:?T?|?null;
          ??loading:?boolean;
          ??cancel:?Canceler;
          ??err?:?ErrorData;
          }

          export?interface?ErrorData?{
          ??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:?'超時',
          ??????data:?null
          ????});
          ??}
          ??console.log('response-error',?err.toJSON());
          ??return?Promise.reject(err);
          });

          export?function?request<T>(config:?AxiosRequestConfig):?RequestResponse<T>?{
          ??const?cancel?=?ref();
          ??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,
          ??options:?Options?=?{}
          )?
          {
          ??const?{
          ????manual?=?false,
          ????defaultParams?=?[]?as?unknown?as?P,
          ????repeatCancel?=?false,
          ????refreshDeps?=?null,
          ????refreshDepsParams?=?null,
          ????queryKey?=?null
          ??}?=?options;

          ??const?querise?=?reactivestring?|?symbol,?IRequestResult>>({
          ????[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é)流 錯誤重試等功能,僅需要擴展Options類型,在useRequest中添加對應的邏輯即可,比如使用lodash包裹run函數(shù),這里只是將最基本的功能實現(xiàn)搞定了,一部分小問題以及擴展性的東西沒有過分糾結。

          結語

          當前的 useRequest 還是比較簡陋,希望有想法或者建議的朋友可以一起討論,有什么問題也可以問我,謝謝。

          感謝

          本次分享到這里就結束了,感謝您的閱讀,如果本文對您有什么幫助,別忘了動動手指點個贊 ?? 和關注。

          Node 社群



          我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學習感興趣的話(后續(xù)有計劃也可以),我們可以一起進行Node.js相關的交流、學習、共建。下方加 考拉 好友回復「Node」即可。



          如果你覺得這篇內(nèi)容對你有幫助,我想請你幫我2個小忙:

          1. 點個「在看」,讓更多人也能看到這篇文章
          2. 訂閱官方博客?www.inode.club?讓我們一起成長

          點贊和在看就是最大的支持

          瀏覽 83
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产黄色免费在线观看 | 性生活无码 | 黄色美女毛片 | 青青草原免费在线视频 | 亚洲高清无码播放 |