ahooks源码解析之useRequest

573 阅读4分钟

一、useRequest 介绍

useRequest 是一个强大的异步数据管理的 Hooks。

useRequest 通过插件式组织代码,核心代码极其简单,并且可以很方便的扩展出更高级的功能。目前已有能力包括:

  • 自动请求/手动请求
  • 轮询
  • 防抖
  • 节流
  • 屏幕聚焦重新请求
  • 错误重试
  • loading delay
  • SWR(stale-while-revalidate)
  • 缓存

二、源码解析

1、入口文件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, [
    ...(plugins || []),
    useDebouncePlugin,
    useLoadingDelayPlugin,
    usePollingPlugin,
    useRefreshOnWindowFocusPlugin,
    useThrottlePlugin,
    useAutoRunPlugin,
    useCachePlugin,
    useRetryPlugin,
  ] as Plugin<TData, TParams>[]);
}

useRequest入口函数非常简单,返回了useRequestImplement的执行结果。

useRequestImplementuseRequest的具体实现,在useRequest参数的基础上,加入了一些默认的插件。

2、useRequestImplement.ts

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();
​
  // 用useCreation生成实例,避免重复新建实例,提升性能
  const fetchInstance = useCreation(() => {
    // 插件里有onInit钩子的就执行该钩子
    const initState = plugins.map((p) => p?.onInit?.(fetchOptions)).filter(Boolean);
​
    return new Fetch<TData, TParams>(
      serviceRef,
      fetchOptions,
      update,
      Object.assign({}, ...initState),
    );
  }, []);
  fetchInstance.options = fetchOptions;
  // 执行所有的插件
  fetchInstance.pluginImpls = plugins.map((p) => p(fetchInstance, fetchOptions));
​
  useMount(() => {
    // manual为false,自动执行run
    if (!manual) {
      // useCachePlugin can set fetchInstance.state.params from cache when init
      const params = fetchInstance.state.params || options.defaultParams || [];
      // @ts-ignore
      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>;
}

useRequestImplement代码也是很简短,主要做了几件事情:

  • 通过useCreation来生成Fetch的请求实例,useCreation的作用是避免重复新建实例,提升性能。
  • 执行插件里的onInit钩子,并生成初始化的参数。
  • 执行所有的插件。
  • 执行自动请求。
  • 组件卸载时取消请求实例。
  • 返回调用useRequest的参数。

3、Fetch.ts

Fetch的代码较长,我们拆开来一点点看。

3.1 constructor

主要是合并一些初始状态,包括插件执行onInit时返回的状态。

3.2 runAsync

runAsync是手动请求时返回的异步请求方法,后面可以接thencatch,是Fetch里的主要逻辑。

async runAsync(...params: TParams): Promise<TData> {
    // 用来计数,当count和currentCount不一致时,中断请求
    this.count += 1;
    const currentCount = this.count;
    
    // 执行插件的onBefore钩子
    const {
      stopNow = false,
      returnNow = false,
      ...state
    } = this.runPluginHandler('onBefore', params);
​
    // stop request
    if (stopNow) {
      return new Promise(() => {});
    }
​
    this.setState({
      loading: true,
      params,
      ...state,
    });
​
    // return now
    if (returnNow) {
      return Promise.resolve(state.data);
    }
    
    // 执行useRequest参数里的onBefore回调
    this.options.onBefore?.(params);
​
    try {
      // 执行onRequest钩子,返回promise
      let { servicePromise } = this.runPluginHandler('onRequest', this.serviceRef.current, params);
​
      // 如果上面没有返回promise,则执行请求服务,返回一个promise
      if (!servicePromise) {
        servicePromise = this.serviceRef.current(...params);
      }
​
      // 执行服务的promise
      const res = await servicePromise;
​
      // 当请求取消时,count会不等,停止请求
      if (currentCount !== this.count) {
        // prevent run.then when request is canceled
        return new Promise(() => {});
      }
​
      this.setState({
        data: res,
        error: undefined,
        loading: false,
      });
​
      // 执行onSuccess和onFinally钩子
      this.options.onSuccess?.(res, params);
      this.runPluginHandler('onSuccess', res, params);
​
      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和onFinally钩子
      this.options.onError?.(error, params);
      this.runPluginHandler('onError', error, params);
​
      this.options.onFinally?.(params, undefined, error);
​
      if (currentCount === this.count) {
        this.runPluginHandler('onFinally', params, undefined, error);
      }
​
      throw error;
    }

runAsync代码虽然多,但是只是做了几件事情:

  • 执行请求服务。
  • 当请求取消时,退出请求。
  • 执行onBefore、onRequest、onSuccess、onError、onFinally钩子。
  • 返回成功和失败的promise

3.3 run

runrunAsync的基础上,捕获打印了错误,不再返回promise

run(...params: TParams) {
    this.runAsync(...params).catch((error) => {
      if (!this.options.onError) {
        console.error(error);
      }
    });
  }

3.4 cancel

cancel方法很简单,就是让count+1,使它不等于当前的计数,使loading变为false,然后执行插件的onCancel取消钩子

cancel() {
    this.count += 1;
    this.setState({
      loading: false,
    });
​
    this.runPluginHandler('onCancel');
 }

3.5 mutate

mutate是手动更改返回参数,使页面更早地响应更改结果,而不必等接口响应。

mutate(data?: TData | ((oldData?: TData) => TData | undefined)) {
    let targetData: TData | undefined;
    if (isFunction(data)) {
      // @ts-ignore
      targetData = data(this.state.data);
    } else {
      targetData = data;
    }
​
    this.runPluginHandler('onMutate', targetData);
​
    this.setState({
      data: targetData,
    });
  }

做的事情很简单,如果传入参数为函数,先执行函数获取返回参数,然后把数据设置给useRequest的返回值data即可。另外还执行了插件的onMutate钩子。

3.5 其它方法

其它的方法都比较简单,不再一一解析源码,他们分别是:

  • setState:设置状态,并且更新。
  • runPluginHandler:执行插件的指定钩子。
  • refresh:使用原来的参数重新请求。
  • refreshAsync:使用原来的参数重新请求,返回异步promise。

三、插件机制

插件就是在某些生命周期执行某些逻辑的方法。

useRequest封装了许多默认的插件,同时还支持用户自定义插件。他们在useRequest创建实例后执行。

fetchInstance.pluginImpls = plugins.map((p) => p(fetchInstance, fetchOptions));

插件执行后返回一个对象,这个对象上挂载了一些特定生命周期执行的方法,在合适的时机时调用他们。

return {
    onBefore: () => {},
    onSuccess: () => {},
    ...
}
this.runPluginHandler('onSuccess', res, params);

可以看到,插件的生命周期钩子是通过封装的runPluginHandler方法调用的。

runPluginHandler方法很简单,就是执行所有插件的该生命周期。

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);
}