成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

插件化機制"美麗"封裝你的hook請求使用方式解析

3403771864 / 643人閱讀

      我們講下 ahooks 的核心 hook —— useRequest。

  useRequest 簡介

  根據(jù)官方文檔的介紹,useRequest 是一個強大的異步數(shù)據(jù)管理的 Hooks,React 項目中的網(wǎng)絡請求場景使用 useRequest ,這就可以。

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

  自動請求/手動請求

  輪詢

  防抖

  節(jié)流

  屏幕聚焦重新請求

  錯誤重試

  loading delay

  SWR(stale-while-revalidate)

  緩存

  這樣看起來 useRequest 的功能是不是非常的強大,要是會如何實現(xiàn)?也可以從介紹中看到官方的答案——插件化機制。

  架構(gòu)

1.png

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

  入口 useRequest。它負責的是初始化處理數(shù)據(jù)以及將結(jié)果返回。

  Fetch。是整個 useRequest 的核心代碼,它處理了整個請求的生命周期。

  plugin。在 Fetch 中,會通過插件化機制在不同的時機觸發(fā)不同的插件方法,拓展 useRequest 的功能特性。

  utils 和 types.ts。提供工具方法以及類型定義。

  useRequest 入口處理

  先從入口文件開始,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, [
  // 插件列表,用來拓展功能,一般用戶不使用。文檔中沒有看到暴露 API
  ...(plugins || []),
  useDebouncePlugin,
  useLoadingDelayPlugin,
  usePollingPlugin,
  useRefreshOnWindowFocusPlugin,
  useThrottlePlugin,
  useAutoRunPlugin,
  useCachePlugin,
  useRetryPlugin,
  ] as Plugin<TData, TParams>[]);
  }
  export default useRequest;

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

  可以看到返回了useRequestImplement方法。主要是對 Fetch 類進行實例化?!?/p>

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

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

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

  另外這個文件做的就是將結(jié)果返回給開發(fā)者了,這點不細說。

  Fetch 和 Plugins

  接下來最核心的源碼部分 —— Fetch 類。其代碼不多,算是非常精簡,先簡化一下:

  export default class Fetch<TData, TParams extends any[]> {
  // 插件執(zhí)行后返回的方法列表
  pluginImpls: PluginReturn<TData, TParams>[];
  count: number = 0;
  // 幾個重要的返回值
  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, // 非手動,就loading
  ...initState,
  };
  }
  // 更新狀態(tài)
  setState(s: Partial<FetchState<TData, TParams>> = {}) {
  this.state = {
  ...this.state,
  ...s,
  };
  this.subscribe();
  }
  // 執(zhí)行插件中的某個事件(event),rest 為參數(shù)傳入
  runPluginHandler(event: keyof PluginReturn<TData, TParams>, ...rest: any[]) {
  // 省略代碼...
  }
  // 如果設(shè)置了 options.manual = true,則 useRequest 不會默認執(zhí)行,需要通過 run 或者 runAsync 來觸發(fā)執(zhí)行。
  // runAsync 是一個返回 Promise 的異步函數(shù),如果使用 runAsync 來調(diào)用,則意味著你需要自己捕獲異常。
  async runAsync(...params: TParams): Promise<TData> {
  // 省略代碼...
  }
  // run 是一個普通的同步函數(shù),其內(nèi)部也是調(diào)用了 runAsync 方法
  run(...params: TParams) {
  // 省略代碼...
  }
  // 取消當前正在進行的請求
  cancel() {
  // 省略代碼...
  }
  // 使用上一次的 params,重新調(diào)用 run
  refresh() {
  // 省略代碼...
  }
  // 使用上一次的 params,重新調(diào)用 runAsync
  refreshAsync() {
  // 省略代碼...
  }
  // 修改 data。參數(shù)可以為函數(shù),也可以是一個值
  mutate(data?: TData | ((oldData?: TData) => TData | undefined)) {
  // 省略代碼...
  }

  state 以及 setState

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

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

  插件化機制的實現(xià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;
  }

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

2.png

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

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

  // 執(zhí)行插件中的某個事件(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);
  }

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

  符合職責單一原則。一個 Plugin 只做一件事,相互之間不相關(guān)。整體的可維護性更高,并且擁有更好的可測試性。

  符合深模塊的軟件設(shè)計理念。其認為最好的模塊提供了強大的功能,又有著簡單的接口。試想每個模塊由一個長方形表示,如下圖,長方形的面積大小和模塊實現(xiàn)的功能多少成比例。頂部邊代表模塊的接口,邊的長度代表它的復雜度。最好的模塊是深的:他們有很多功能隱藏在簡單的接口后。深模塊是好的抽象,因為它只把自己內(nèi)部的一小部分復雜度暴露給了用戶。

3.png

  核心方法 —— runAsync

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

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

4.png

  請求前 —— onBefore

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

 

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

  進行請求——onRequest

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

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

  useCachePlugin 返回的 onRequest 方法:

  // 請求階段
  onRequest: (service, args) => {
  // 看 promise 有沒有緩存
  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 };
  },

  取消請求 —— onCancel

  剛剛在請求開始前定義了 currentCount 變量,其實為了 cancel 請求。

  this.count += 1;
  // 主要為了 cancel 請求
  const currentCount = this.count;

  在請求過程中,開發(fā)者可以調(diào)用 Fetch 的 cancel 方法:

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

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

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

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

  這部分也就比較簡單了,通過 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í)行完成時觸發(fā)
  this.options.onFinally?.(params, res, undefined);
  if (currentCount === this.count) {
  // plugin 中 onFinally 事件
  this.runPluginHandler('onFinally', params, res, undefined);
  }
  return res;
  // 捕獲報錯
  } catch (error) {
  // 省略代碼...
  // service reject 時觸發(fā)
  this.options.onError?.(error, params);
  // 執(zhí)行 plugin 中的 onError 事件
  this.runPluginHandler('onError', error, params);
  // service 執(zhí)行完成時觸發(fā)
  this.options.onFinally?.(params, undefined, error);
  if (currentCount === this.count) {
  // plugin 中 onFinally 事件
  this.runPluginHandler('onFinally', params, undefined, error);
  }
  // 拋出錯誤。
  // 讓外部捕獲感知錯誤
  throw error;
  }

  思考與總結(jié)

  useRequest 是 ahooks 最核心的功能之一,它的功能非常豐富,可核心代碼(Fetch 類)卻很簡便,這就是插件化機制優(yōu)勢,可以把特定功能交給特定的插件去實現(xiàn),這樣就只需負責主流程的設(shè)計,并暴露相應的執(zhí)行時機即可。

  組件/hook 封裝真的作用十分大,當我們面對對一個復雜功能的抽象,可以盡可能保證對外接口簡單。內(nèi)部實現(xiàn)需要遵循單一職責的原則,通過類似插件化的機制,細化拆分組件,從而提升組件可維護性、可測試性。

         希望大家都可以好好學習,多多實踐操作。



文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/128266.html

相關(guān)文章

  • ahooks useRequest源碼深入解讀

      大家會發(fā)現(xiàn),自從 React v16.8 推出了 Hooks API,前端框架圈并開啟了新的邏輯復用的時代,從此無需在意 HOC 的無限套娃導致性能差的問題,同時也解決了 mixin 的可閱讀性差的問題。這里也有對于 React 最大的變化是函數(shù)式組件可以有自己的狀態(tài),扁平化的邏輯組織方式,更加友好地支持 TS 類型聲明。  在運用Hooks的時候,除了 React 官方提供的,同時也支持我們...

    3403771864 評論0 收藏0
  • Android 插件原理學習 —— Hook 機制之動態(tài)代理

    摘要:什么樣的對象容易找到靜態(tài)變量和單例。在一個進程之內(nèi),靜態(tài)變量和單例變量是相對不容易發(fā)生變化的,因此非常容易定位,而普通的對象則要么無法標志,要么容易改變。 前言 為了實現(xiàn) App 的快速迭代更新,基于 H5 Hybrid 的解決方案有很多,由于 webview 本身的性能問題,也隨之出現(xiàn)了很多基于 JS 引擎實現(xiàn)的原生渲染的方案,例如 React Native、weex 等,而國內(nèi)一線...

    gekylin 評論0 收藏0
  • FE.SRC-webpack原理梳理

    摘要:執(zhí)行完成后會返回如下圖的結(jié)果,根據(jù)返回數(shù)據(jù)把源碼和存儲在的屬性上的回調(diào)函數(shù)中調(diào)用類生成,并根據(jù)生成依賴后回調(diào)方法返回類。 webpack設(shè)計模式 一切資源皆Module Module(模塊)是webpack的中的關(guān)鍵實體。Webpack 會從配置的 Entry 開始遞歸找出所有依賴的模塊. 通過Loaders(模塊轉(zhuǎn)換器),用于把模塊原內(nèi)容按照需求轉(zhuǎn)換成新模塊內(nèi)容. 事件驅(qū)動架構(gòu) we...

    cfanr 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<