ahooks useRequest 源码阅读之核心代码

56 阅读4分钟

大家平时开发的时候一定用到过ahooks 的useRequest 请求后端数据吧,用了一段时间后,你是否也和我一样好奇这个hook到底是怎么实现的呢,那么今天就让我们一起来看看它的源码吧。
首先我们来看下这个hook 的架构图, 从如下图可以清楚的看到useRequest 主要分为3个模块: useRequestImplement(Core), Plugins, utils。今天我们主要讲下它的核心模块。

截图.png

useRequest

首先我们从useRequest 入手。 useRequest 主要是通过 useRequestImplement 这个函数将各个插件集成起来的。从- 源码上来看,通过useRequest 第三个参数plugins,我们可以自己扩展插件,至于怎么扩展,我们下一篇讲插件的时候会说道。

function useRequest<TData, TParams extends any[]>(
  service: Service<TData, TParams>,
  options?: Options<TData, TParams>,
  plugins?: Plugin<TData, TParams>[],
) {
  return useRequestImplement<TData, TParams>(service, options, [
    ...(plugins || []),
    useDebouncePlugin,
    useLoadingDelayPlugin,
    usePollingPlugin,
    useRefreshOnWindowFocusPlugin,
    useThrottlePlugin,
    useAutoRunPlugin,
    useCachePlugin,
    useRetryPlugin,
  ] as Plugin<TData, TParams>[]);
}

export default useRequest;

useRequestImplement

接着我们来看下这个方法,它主要实现了以下内容:

  • 获取请求实例;

  • 判断挂载时是否自动请求;

  • 获取每个插件返回的初始化状态值用以重置Fetch 中的 state 值;

  • 获取每个插件设置的钩子函数,在请求的不同生命周期执行;

  • 返回我们要使用的各个数据和方法;

    function useRequestImplement<TData, TParams extends any[]>( service: Service<TData, TParams>, options: Options<TData, TParams> = {}, plugins: Plugin<TData, TParams>[] = [], ) { const { manual = false, ...rest } = options; // 默认自动请求 const fetchOptions = { manual, ...rest, }; const serviceRef = useLatest(service); // 获取最新的请求方法 const update = useUpdate(); // 强制刷新 const fetchInstance = useCreation(() => { // 运行每个插件的onInit 方法, 返回初始状态值,重置Fetch 中的state。内置插件只有 useAutoRunPlugin 用到 const initState = plugins.map((p) => p?.onInit?.(fetchOptions)).filter(Boolean); return new Fetch<TData, TParams>( serviceRef, fetchOptions, update, Object.assign({}, ...initState), ); }, []); fetchInstance.options = fetchOptions; // 执行所有插件,返回每个插件的钩子函数, 在Fetch中请求的不同生命周期中调用 fetchInstance.pluginImpls = plugins.map((p) => p(fetchInstance, fetchOptions)); // 挂载时是否自动请求 useMount(() => { if (!manual) { const params = fetchInstance.state.params || options.defaultParams || []; fetchInstance.run(...params); } }); useUnmount(() => { fetchInstance.cancel(); }); // 向外抛出状态,数据与方法 return { loading: fetchInstance.state.loading, data: fetchInstance.state.data, error: fetchInstance.state.error, params: fetchInstance.state.params || [], cancel: useMemoizedFn(fetchInstance.cancel.bind(fetchInstance)), refresh: useMemoizedFn(fetchInstance.refresh.bind(fetchInstance)), refreshAsync: useMemoizedFn(fetchInstance.refreshAsync.bind(fetchInstance)), run: useMemoizedFn(fetchInstance.run.bind(fetchInstance)), runAsync: useMemoizedFn(fetchInstance.runAsync.bind(fetchInstance)), mutate: useMemoizedFn(fetchInstance.mutate.bind(fetchInstance)), } as Result<TData, TParams>; } export default useRequestImplement;

Fetch

在看Fetch 这个核心方法之前,我们先来看一个TS 类型 PluginReturn,以及这个方法中的2个核心函数, runPluginHandlerrunAsync, 以方便理解后面代码。

// 插件返回的钩子函数的定义,看钩子函数的名字可以很好理解每个钩子函数是在请求的哪个生命周期被调用
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;
}

// 执行插件的钩子函数的工具方法,并返回一些状态值。
  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);
  }

// 异步请求,并在相应生命周期执行插件的钩子函数以及用户传进来的钩子函数。
  async runAsync(...params: TParams): Promise<TData> {
    this.count += 1; // 用来判断请求是否被取消
    const currentCount = this.count; 
    const {
      stopNow = false, // useAutoRunPlugin 中,read 为false 时,会返回true,从而阻止触发请求
      returnNow = false, // useCachePlugin 中,如果设置数据是新鲜的,则返回true,从而返回缓存中的数据 
      ...state // useCachePlugin 中如果缓存有值,则用缓存的值重置 Fetch 中的state.data
    } = this.runPluginHandler('onBefore', params);
    // 阻止触发请求
    if (stopNow) {
      return new Promise(() => {});
    }
    this.setState({
      loading: true,
      params,
      ...state,
    });
    // 返回缓存的值
    if (returnNow) {
      return Promise.resolve(state.data);
    }
    // 调用者自己传的onBefore钩子函数
    this.options.onBefore?.(params);
    try {
      // 只有 useCachePlugin 中有返回,获取缓存中的请求方法
      let { servicePromise } = this.runPluginHandler('onRequest', this.serviceRef.current, params);  
      // 如果没有缓存请求方法则用传入的service 进行请求
      if (!servicePromise) {
        servicePromise = this.serviceRef.current(...params);
      }
      const res = await servicePromise;
      // 如果调用了cancel, this.count 会 +1, 所以如果这里不等,说明被cancel了。
      if (currentCount !== this.count) {
        return new Promise(() => {});
      }
      // 成功后重置内部 Fetch中的state
      this.setState({
        data: res,
        error: undefined,
        loading: false,
      });
      // onSuccess 生命周期  
      this.options.onSuccess?.(res, params);
      this.runPluginHandler('onSuccess', res, params);
      // onFinally生命周期 
      this.options.onFinally?.(params, res, undefined);
      if (currentCount === this.count) {
        this.runPluginHandler('onFinally', params, res, undefined);
      }
      return res;
    } catch (error) {
      if (currentCount !== this.count) {
        // prevent run.then when request is canceled
        return new Promise(() => {});
      }
      this.setState({
        error,
        loading: false,
      });
      // onError 生命周期 
      this.options.onError?.(error, params);
      this.runPluginHandler('onError', error, params);
      // onFinally 生命周期
      this.options.onFinally?.(params, undefined, error);
      if (currentCount === this.count) {
        this.runPluginHandler('onFinally', params, undefined, error);
      }
      throw error;
    }
  }

接下来我们来看看 Fetch 里是怎么调用这2个函数来处理不同的请求逻辑的。

export default class Fetch<TData, TParams extends any[]> {
  pluginImpls: PluginReturn<TData, TParams>[]; // 所有插件的钩子集合数组
  count: number = 0; // 计数器,用来判断请求是否被取消
  state: FetchState<TData, TParams> = { // 初始化值
    loading: false,
    params: undefined,
    data: undefined,
    error: undefined,
  };
  constructor(
    public serviceRef: MutableRefObject<Service<TData, TParams>>,
    public options: Options<TData, TParams>,
    public subscribe: Subscribe,
    public initState: Partial<FetchState<TData, TParams>> = {},
  ) {
     // 这里loading会根据是否是自动请求判断,后面也会被initState里面useAutoRunPlugin的onInit的返回状态重置
    this.state = {
      ...this.state,
      loading: !options.manual,
      ...initState,
    };
  }
  // 模拟了class 里的setState,更新state后强制刷新
  setState(s: Partial<FetchState<TData, TParams>> = {}) {
    this.state = {
      ...this.state,
      ...s,
    };
    this.subscribe();
  }
  // 执行插件的钩子函数的工具方法
  runPluginHandler(event: keyof PluginReturn<TData, TParams>, ...rest: any[]) {
     ... // 省略代码
  }
  async runAsync(...params: TParams): Promise<TData> {
     ... // 省略代码
  }
  run(...params: TParams) {
    // run 可以自己捕获异常
    this.runAsync(...params).catch((error) => {
      if (!this.options.onError) {
        console.error(error);
      }
    });
  }
  cancel() {
    this.count += 1; // 用来判断是否被取消请求
    this.setState({
      loading: false,
    });
    // 调用插件的onCancel 钩子函数
    this.runPluginHandler('onCancel');
  }
  refresh() {
    this.run(...(this.state.params || []));
  }
  refreshAsync() {
    return this.runAsync(...(this.state.params || []));
  }
  mutate(data?: TData | ((oldData?: TData) => TData | undefined)) {
    let targetData: TData | undefined;
    if (isFunction(data)) {
      targetData = data(this.state.data);
    } else {
      targetData = data;
    }
    this.runPluginHandler('onMutate', targetData);
    this.setState({
      data: targetData,
    });
  }
}

看到这里,我们的核心代码就阅读完了,下一篇我们将一起来阅读插件和部分工具hooks的源码。