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

插件化機(jī)制優(yōu)雅封裝你的hook請(qǐng)求使用方式

 更新時(shí)間:2022年07月11日 11:59:00   作者:Gopal  
這篇文章主要為大家介紹了插件化機(jī)制優(yōu)雅封裝你的hook請(qǐng)求使用方式示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

本文是深入淺出 ahooks 源碼系列文章的第二篇,這個(gè)系列的目標(biāo)主要有以下幾點(diǎn):

  • 加深對(duì) React hooks 的理解。
  • 學(xué)習(xí)如何抽象自定義 hooks。構(gòu)建屬于自己的 React hooks 工具庫(kù)。
  • 培養(yǎng)閱讀學(xué)習(xí)源碼的習(xí)慣,工具庫(kù)是一個(gè)對(duì)源碼閱讀不錯(cuò)的選擇。

注:本系列對(duì) ahooks 的源碼解析是基于 v3.3.13。自己 folk 了一份源碼,主要是對(duì)源碼做了一些解讀,可見(jiàn) 詳情

系列文章:大家都能看得懂的源碼(一)ahooks 整體架構(gòu)篇

本文來(lái)講下 ahooks 的核心 hook —— useRequest。

useRequest 簡(jiǎn)介

根據(jù)官方文檔的介紹,useRequest 是一個(gè)強(qiáng)大的異步數(shù)據(jù)管理的 Hooks,React 項(xiàng)目中的網(wǎng)絡(luò)請(qǐng)求場(chǎng)景使用 useRequest 就夠了。

useRequest 通過(guò)插件式組織代碼,核心代碼極其簡(jiǎn)單,并且可以很方便的擴(kuò)展出更高級(jí)的功能。目前已有能力包括:

  • 自動(dòng)請(qǐng)求/手動(dòng)請(qǐng)求
  • 輪詢
  • 防抖
  • 節(jié)流
  • 屏幕聚焦重新請(qǐng)求
  • 錯(cuò)誤重試
  • loading delay
  • SWR(stale-while-revalidate)
  • 緩存

這里可以看到 useRequest 的功能是非常強(qiáng)大的,如果讓你來(lái)實(shí)現(xiàn),你會(huì)如何實(shí)現(xiàn)?也可以從介紹中看到官方的答案——插件化機(jī)制。

架構(gòu)

如上圖所示,我把整個(gè) useRequest 分成了幾個(gè)模塊。

  • 入口 useRequest。它負(fù)責(zé)的是初始化處理數(shù)據(jù)以及將結(jié)果返回。
  • Fetch。是整個(gè) useRequest 的核心代碼,它處理了整個(gè)請(qǐng)求的生命周期。
  • plugin。在 Fetch 中,會(huì)通過(guò)插件化機(jī)制在不同的時(shí)機(jī)觸發(fā)不同的插件方法,拓展 useRequest 的功能特性。
  • utils 和 types.ts。提供工具方法以及類型定義。

useRequest 入口處理

先從入口文件開(kāi)始,packages/hooks/src/useRequest/src/useRequest.ts。

function useRequest<TData, TParams extends any[]>(
  service: Service<TData, TParams>,
  options?: Options<TData, TParams>,
  plugins?: Plugin<TData, TParams>[],
) {
  return useRequestImplement<TData, TParams>(service, options, [
    // 插件列表,用來(lái)拓展功能,一般用戶不使用。文檔中沒(méi)有看到暴露 API
    ...(plugins || []),
    useDebouncePlugin,
    useLoadingDelayPlugin,
    usePollingPlugin,
    useRefreshOnWindowFocusPlugin,
    useThrottlePlugin,
    useAutoRunPlugin,
    useCachePlugin,
    useRetryPlugin,
  ] as Plugin<TData, TParams>[]);
}
export default useRequest;

這里第一(service 請(qǐng)求實(shí)例)第二個(gè)參數(shù)(配置選項(xiàng)),我們比較熟悉,第三個(gè)參數(shù)文檔中沒(méi)有提及,其實(shí)就是插件列表,用戶可以自定義插件拓展功能。

可以看到返回了 useRequestImplement 方法。主要是對(duì) Fetch 類進(jìn)行實(shí)例化。

const update = useUpdate();
// 保證請(qǐng)求實(shí)例都不會(huì)發(fā)生改變
const fetchInstance = useCreation(() => {
  // 目前只有 useAutoRunPlugin 這個(gè) plugin 有這個(gè)方法
  // 初始化狀態(tài),返回 { loading: xxx },代表是否 loading
  const initState = plugins.map((p) => p?.onInit?.(fetchOptions)).filter(Boolean);
  // 返回請(qǐng)求實(shí)例
  return new Fetch<TData, TParams>(
    serviceRef,
    fetchOptions,
    // 可以 useRequestImplement 組件
    update,
    Object.assign({}, ...initState),
  );
}, []);
fetchInstance.options = fetchOptions;
// run all plugins hooks
// 執(zhí)行所有的 plugin,拓展能力,每個(gè) plugin 中都返回的方法,可以在特定時(shí)機(jī)執(zhí)行
fetchInstance.pluginImpls = plugins.map((p) => p(fetchInstance, fetchOptions));

實(shí)例化的時(shí)候,傳參依次為請(qǐng)求實(shí)例,options 選項(xiàng),父組件的更新函數(shù),初始狀態(tài)值。

這里需要非常留意的一點(diǎn)是最后一行,它執(zhí)行了所有的 plugins 插件,傳入的是 fetchInstance 實(shí)例以及 options 選項(xiàng),返回的結(jié)果賦值給 fetchInstance 實(shí)例的 pluginImpls。

另外這個(gè)文件做的就是將結(jié)果返回給開(kāi)發(fā)者了,這點(diǎn)不細(xì)說(shuō)。

Fetch 和 Plugins

接下來(lái)最核心的源碼部分 —— Fetch 類。其代碼不多,算是非常精簡(jiǎn),先簡(jiǎn)化一下:

export default class Fetch<TData, TParams extends any[]> {
  // 插件執(zhí)行后返回的方法列表
  pluginImpls: PluginReturn<TData, TParams>[];
  count: number = 0;
  // 幾個(gè)重要的返回值
  state: FetchState<TData, TParams> = {
    loading: false,
    params: undefined,
    data: undefined,
    error: undefined,
  };
  constructor(
    // React.MutableRefObject —— useRef創(chuàng)建的類型,可以修改
    public serviceRef: MutableRefObject<Service<TData, TParams>>,
    public options: Options<TData, TParams>,
    // 訂閱-更新函數(shù)
    public subscribe: Subscribe,
    // 初始值
    public initState: Partial<FetchState<TData, TParams>> = {},
  ) {
    this.state = {
      ...this.state,
      loading: !options.manual, // 非手動(dòng),就loading
      ...initState,
    };
  }
  // 更新?tīng)顟B(tài)
  setState(s: Partial<FetchState<TData, TParams>> = {}) {
    this.state = {
      ...this.state,
      ...s,
    };
    this.subscribe();
  }
  // 執(zhí)行插件中的某個(gè)事件(event),rest 為參數(shù)傳入
  runPluginHandler(event: keyof PluginReturn<TData, TParams>, ...rest: any[]) {
    // 省略代碼...
  }
  // 如果設(shè)置了 options.manual = true,則 useRequest 不會(huì)默認(rèn)執(zhí)行,需要通過(guò) run 或者 runAsync 來(lái)觸發(fā)執(zhí)行。
  // runAsync 是一個(gè)返回 Promise 的異步函數(shù),如果使用 runAsync 來(lái)調(diào)用,則意味著你需要自己捕獲異常。
  async runAsync(...params: TParams): Promise<TData> {
    // 省略代碼...
  }
  // run 是一個(gè)普通的同步函數(shù),其內(nèi)部也是調(diào)用了 runAsync 方法
  run(...params: TParams) {
    // 省略代碼...
  }
  // 取消當(dāng)前正在進(jìn)行的請(qǐng)求
  cancel() {
    // 省略代碼...
  }
  // 使用上一次的 params,重新調(diào)用 run
  refresh() {
    // 省略代碼...
  }
  // 使用上一次的 params,重新調(diào)用 runAsync
  refreshAsync() {
    // 省略代碼...
  }
  // 修改 data。參數(shù)可以為函數(shù),也可以是一個(gè)值
  mutate(data?: TData | ((oldData?: TData) => TData | undefined)) {
    // 省略代碼...
}

state 以及 setState

在 constructor 中,主要是進(jìn)行了數(shù)據(jù)的初始化。其中維護(hù)的數(shù)據(jù)主要包含一下幾個(gè)重要的數(shù)據(jù)以及通過(guò) setState 方法設(shè)置數(shù)據(jù),設(shè)置完成通過(guò) subscribe 調(diào)用通知 useRequestImplement 組件重新渲染,從而獲取最新值。

// 幾個(gè)重要的返回值
state: FetchState<TData, TParams> = {
  loading: false,
  params: undefined,
  data: undefined,
  error: undefined,
};
// 更新?tīng)顟B(tài)
setState(s: Partial<FetchState<TData, TParams>> = {}) {
  this.state = {
    ...this.state,
    ...s,
  };
  this.subscribe();
}

插件化機(jī)制的實(shí)現(xiàn)

上文有提到所有的插件運(yùn)行的結(jié)果都賦值給 pluginImpls。它的類型定義如下:

export interface PluginReturn<TData, TParams extends any[]> {
  onBefore?: (params: TParams) =>
    | ({
        stopNow?: boolean;
        returnNow?: boolean;
      } & Partial<FetchState<TData, TParams>>)
    | void;
  onRequest?: (
    service: Service<TData, TParams>,
    params: TParams,
  ) => {
    servicePromise?: Promise<TData>;
  };
  onSuccess?: (data: TData, params: TParams) => void;
  onError?: (e: Error, params: TParams) => void;
  onFinally?: (params: TParams, data?: TData, e?: Error) => void;
  onCancel?: () => void;
  onMutate?: (data: TData) => void;
}

除了最后一個(gè) onMutate 之外,可以看到返回的方法都是在一個(gè)請(qǐng)求的生命周期中的。一個(gè)請(qǐng)求從開(kāi)始到結(jié)束,如下圖所示:

如果你比較仔細(xì),你會(huì)發(fā)現(xiàn)基本所有的插件功能都是在一個(gè)請(qǐng)求的一個(gè)或者多個(gè)階段中實(shí)現(xiàn)的,也就是說(shuō)我們只需要在請(qǐng)求的相應(yīng)階段,執(zhí)行我們的插件的邏輯,就能完成我們插件的功能

執(zhí)行特定階段插件方法的函數(shù)為 runPluginHandler,其 event 入?yún)⒕褪巧厦?PluginReturn key 值。

// 執(zhí)行插件中的某個(gè)事件(event),rest 為參數(shù)傳入
runPluginHandler(event: keyof PluginReturn<TData, TParams>, ...rest: any[]) {
  // @ts-ignore
  const r = this.pluginImpls.map((i) => i[event]?.(...rest)).filter(Boolean);
  return Object.assign({}, ...r);
}

通過(guò)這樣的方式,F(xiàn)etch 類的代碼會(huì)變得非常的精簡(jiǎn),只需要完成整體流程的功能,所有額外的功能(比如重試、輪詢等等)都交給插件去實(shí)現(xiàn)。這么做的優(yōu)點(diǎn):

  • 符合職責(zé)單一原則。一個(gè) Plugin 只做一件事,相互之間不相關(guān)。整體的可維護(hù)性更高,并且擁有更好的可測(cè)試性。
  • 符合深模塊的軟件設(shè)計(jì)理念。其認(rèn)為最好的模塊提供了強(qiáng)大的功能,又有著簡(jiǎn)單的接口。試想每個(gè)模塊由一個(gè)長(zhǎng)方形表示,如下圖,長(zhǎng)方形的面積大小和模塊實(shí)現(xiàn)的功能多少成比例。頂部邊代表模塊的接口,邊的長(zhǎng)度代表它的復(fù)雜度。最好的模塊是深的:他們有很多功能隱藏在簡(jiǎn)單的接口后。深模塊是好的抽象,因?yàn)樗话炎约簝?nèi)部的一小部分復(fù)雜度暴露給了用戶。

核心方法 —— runAsync

可以看到 runAsync 是運(yùn)行請(qǐng)求的最核心方法,其他的方法比如 run/refresh/refreshAsync 最終都是調(diào)用該方法。

并且該方法中就可以看到整體請(qǐng)求的生命周期的處理。這跟上面插件返回的方法設(shè)計(jì)是保持一致的。

請(qǐng)求前 —— onBefore

處理請(qǐng)求前的狀態(tài),并執(zhí)行 Plugins 返回的 onBefore 方法,并根據(jù)返回值執(zhí)行相應(yīng)的邏輯。比如,useCachePlugin 如果還存于新鮮時(shí)間內(nèi),則不用請(qǐng)求,返回 returnNow,這樣就會(huì)直接返回緩存的數(shù)據(jù)。

this.count += 1;
// 主要為了 cancel 請(qǐng)求
const currentCount = this.count;
const {
  stopNow = false,
  returnNow = false,
  ...state
  // 先執(zhí)行每個(gè)插件的前置函數(shù)
} = this.runPluginHandler('onBefore', params);
// stop request
if (stopNow) {
  return new Promise(() => {});
}
this.setState({
  // 開(kāi)始 loading
  loading: true,
  // 請(qǐng)求參數(shù)
  params,
  ...state,
});
// return now
// 立即返回,跟緩存策略有關(guān)
if (returnNow) {
  return Promise.resolve(state.data);
}
// onBefore - 請(qǐng)求之前觸發(fā)
// 假如有緩存數(shù)據(jù),則直接返回
this.options.onBefore?.(params);

進(jìn)行請(qǐng)求——onRequest

這個(gè)階段只有 useCachePlugin 執(zhí)行了 onRequest 方法,執(zhí)行后返回 service Promise(有可能是緩存的結(jié)果),從而達(dá)到緩存 Promise 的效果。

// replace service
// 如果有 cache 的實(shí)例,則使用緩存的實(shí)例
let { servicePromise } = this.runPluginHandler('onRequest', this.serviceRef.current, params);
if (!servicePromise) {
  servicePromise = this.serviceRef.current(...params);
}
const res = await servicePromise;

useCachePlugin 返回的 onRequest 方法:

// 請(qǐng)求階段
onRequest: (service, args) => {
  // 看 promise 有沒(méi)有緩存
  let servicePromise = cachePromise.getCachePromise(cacheKey);
  // If has servicePromise, and is not trigger by self, then use it
  // 如果有servicePromise,并且不是自己觸發(fā)的,那么就使用它
  if (servicePromise && servicePromise !== currentPromiseRef.current) {
    return { servicePromise };
  }
  servicePromise = service(...args);
  currentPromiseRef.current = servicePromise;
  // 設(shè)置 promise 緩存
  cachePromise.setCachePromise(cacheKey, servicePromise);
  return { servicePromise };
},

取消請(qǐng)求 —— onCancel

剛剛在請(qǐng)求開(kāi)始前定義了 currentCount 變量,其實(shí)為了 cancel 請(qǐng)求。

this.count += 1;
// 主要為了 cancel 請(qǐng)求
const currentCount = this.count;

在請(qǐng)求過(guò)程中,開(kāi)發(fā)者可以調(diào)用 Fetch 的 cancel 方法:

// 取消當(dāng)前正在進(jìn)行的請(qǐng)求
cancel() {
  // 設(shè)置 + 1,在執(zhí)行 runAsync 的時(shí)候,就會(huì)發(fā)現(xiàn) currentCount !== this.count,從而達(dá)到取消請(qǐng)求的目的
  this.count += 1;
  this.setState({
    loading: false,
  });
  // 執(zhí)行 plugin 中所有的 onCancel 方法
  this.runPluginHandler('onCancel');
}

這個(gè)時(shí)候,currentCount !== this.count,就會(huì)返回空數(shù)據(jù)。

// 假如不是同一個(gè)請(qǐng)求,則返回空的 promise
if (currentCount !== this.count) {
  // prevent run.then when request is canceled
  return new Promise(() => {});
}

最后結(jié)果處理——onSuccess/onError/onFinally

這部分也就比較簡(jiǎn)單了,通過(guò) try...catch...最后成功,就直接在 try 末尾加上 onSuccess 的邏輯,失敗在 catch 末尾加上 onError 的邏輯,兩者都加上 onFinally 的邏輯。

try {
  const res = await servicePromise;
  // 省略代碼...
  this.options.onSuccess?.(res, params);
  // plugin 中 onSuccess 事件
  this.runPluginHandler('onSuccess', res, params);
  // service 執(zhí)行完成時(shí)觸發(fā)
  this.options.onFinally?.(params, res, undefined);
  if (currentCount === this.count) {
    // plugin 中 onFinally 事件
    this.runPluginHandler('onFinally', params, res, undefined);
  }
  return res;
  // 捕獲報(bào)錯(cuò)
} catch (error) {
  // 省略代碼...
  // service reject 時(shí)觸發(fā)
  this.options.onError?.(error, params);
  // 執(zhí)行 plugin 中的 onError 事件
  this.runPluginHandler('onError', error, params);
  // service 執(zhí)行完成時(shí)觸發(fā)
  this.options.onFinally?.(params, undefined, error);
  if (currentCount === this.count) {
    // plugin 中 onFinally 事件
    this.runPluginHandler('onFinally', params, undefined, error);
  }
  // 拋出錯(cuò)誤。
  // 讓外部捕獲感知錯(cuò)誤
  throw error;
}

思考與總結(jié)

useRequest 是 ahooks 最核心的功能之一,它的功能非常豐富,但核心代碼(Fetch 類)相對(duì)簡(jiǎn)單,這得益于它的插件化機(jī)制,把特定功能交給特定的插件去實(shí)現(xiàn),自己只負(fù)責(zé)主流程的設(shè)計(jì),并暴露相應(yīng)的執(zhí)行時(shí)機(jī)即可。

這對(duì)于我們平時(shí)的組件/hook 封裝很有幫助,我們對(duì)一個(gè)復(fù)雜功能的抽象,可以盡可能保證對(duì)外接口簡(jiǎn)單。內(nèi)部實(shí)現(xiàn)需要遵循單一職責(zé)的原則,通過(guò)類似插件化的機(jī)制,細(xì)化拆分組件,從而提升組件可維護(hù)性、可測(cè)試性。

參考

軟件設(shè)計(jì)之Deep Module(深模塊)

精讀 ahooks useRequest 源碼

以上就是插件化機(jī)制優(yōu)雅封裝你的請(qǐng)求hook使用方式的詳細(xì)內(nèi)容,更多關(guān)于插件化封裝請(qǐng)求hook的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 解決webpack -p壓縮打包react報(bào)語(yǔ)法錯(cuò)誤的方法

    解決webpack -p壓縮打包react報(bào)語(yǔ)法錯(cuò)誤的方法

    這篇文章主要給大家介紹了關(guān)于解決webpack -p壓縮打包react報(bào)語(yǔ)法錯(cuò)誤的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。
    2017-07-07
  • react項(xiàng)目中如何引入國(guó)際化

    react項(xiàng)目中如何引入國(guó)際化

    在React項(xiàng)目中引入國(guó)際化可以使用第三方庫(kù)來(lái)實(shí)現(xiàn),本文主要介紹了react項(xiàng)目中如何引入國(guó)際化,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • 詳解React Angular Vue三大前端技術(shù)

    詳解React Angular Vue三大前端技術(shù)

    當(dāng)前世界中,技術(shù)發(fā)展非常迅速并且變化迅速,開(kāi)發(fā)者需要更多的開(kāi)發(fā)工具來(lái)解決不同的問(wèn)題。本文就對(duì)于當(dāng)下主流的前端開(kāi)發(fā)技術(shù)React、Vue、Angular這三個(gè)框架做個(gè)相對(duì)詳盡的探究,目的是為了解開(kāi)這些前端技術(shù)的面紗,看看各自的廬山真面目。
    2021-05-05
  • React 的調(diào)和算法Diffing 算法策略詳解

    React 的調(diào)和算法Diffing 算法策略詳解

    React的調(diào)和算法,主要發(fā)生在render階段,調(diào)和算法并不是一個(gè)特定的算法函數(shù),而是指在調(diào)和過(guò)程中,為提高構(gòu)建workInProcess樹(shù)的性能,以及Dom樹(shù)更新的性能,而采用的一種策略,又稱diffing算法
    2021-12-12
  • React Mobx狀態(tài)管理工具的使用

    React Mobx狀態(tài)管理工具的使用

    這篇文章主要介紹了React Mobx狀態(tài)管理工具的使用,MobX是一個(gè)狀態(tài)管理庫(kù),它會(huì)自動(dòng)收集并追蹤依賴,開(kāi)發(fā)人員不需要手動(dòng)訂閱狀態(tài),當(dāng)狀態(tài)變化之后MobX能夠精準(zhǔn)更新受影響的內(nèi)容,另外它不要求state是可JSON序列化的,也不要求state是immutable
    2023-02-02
  • React腳手架搭建的學(xué)習(xí)

    React腳手架搭建的學(xué)習(xí)

    本文主要介紹了React腳手架搭建的學(xué)習(xí),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • 詳解基于webpack搭建react運(yùn)行環(huán)境

    詳解基于webpack搭建react運(yùn)行環(huán)境

    本篇文章主要介紹了詳解基于webpack搭建react運(yùn)行環(huán)境,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • React更新渲染原理深入分析

    React更新渲染原理深入分析

    什么是re-render(重新渲染)?哪些是必要的re-render?哪些是非必要的re-render?如果你對(duì)這些問(wèn)題還不是很明白,那么可以在這篇文章中找到答案
    2022-12-12
  • 如何去除富文本中的html標(biāo)簽及vue、react、微信小程序中的過(guò)濾器

    如何去除富文本中的html標(biāo)簽及vue、react、微信小程序中的過(guò)濾器

    這篇文章主要介紹了如何去除富文本中的html標(biāo)簽及vue、react、微信小程序中的過(guò)濾器,在vue及react中經(jīng)常會(huì)遇到,今天通過(guò)實(shí)例代碼給大家講解,需要的朋友可以參考下
    2018-11-11
  • ReactJS?應(yīng)用兼容ios9對(duì)標(biāo)ie11解決方案

    ReactJS?應(yīng)用兼容ios9對(duì)標(biāo)ie11解決方案

    這篇文章主要為大家介紹了ReactJS?應(yīng)用兼容ios9對(duì)標(biāo)ie11解決方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01

最新評(píng)論