ahooks源码分析-useRequest

482 阅读4分钟

前言

我将解析ahooks的useRequest源码,将useRequest的功能实现和代码结构解释出来。我主要讲述useRequest的主干,插槽化机制的实现。如果我后续还有动力,我将把剩余的默认的plugin也写下去。如果有问题随时评论,我及时改正。

我的源码学习资料

useRequest的plugin机制

plugin思想

  • plugin机制的设计模式是一种符合职责单一的设计模式
  • plugin举例:一台台式电脑,主机提供接口让显示器 音响 键盘 鼠标 使用,来实现电脑的可视化和更强大的人机交互。
  • 优点就是增强了各种模块的独立性 扩展性 复用性。鼠标与键盘除了调用接口以外需要符合规范,其它可以自由设计,这是独立性与扩展性的体现。鼠标与键盘不仅可以对主机,也可以对那些符合插口规则的机器。这也将更好的在多人合作进行分工。
  • 缺点也是有的,当你的键盘与鼠标要联动时,可电脑开的接口没有这个功能,

useRequest的plugin机制解析

插槽提供的生命周期

  • 运行的生命周期的地方基本在Fetch中。
  • 下面是它插槽中提供了生命周期,可以在运行的某个时间段执行插槽的函数。最后的终点都来onFinally。

image.png

这些生命周期都是在Fetch类中的runAsync中运行

  • onBefore:是请求前的生命周期,返回值中有stopNow为true可以停止请求,返回data为undefined。retrnNow也是停止请求,但会返回停止后会返回data。其余多出的字段统统存入data中。
  • onRequest:也是请求前的生命周期,与onBefore不同的是,生命周期函数可以获得的参数不同,返回值字段的不同。返回有servicePromise的话,会以这个Promise替代掉原本传入请求函数继续处理后续的请求结果
  • 这里种有

useRequestImplement提供给插槽的API

//useRequest的类型声明
useRequest(service,options,plugins) => {data,loading,error}
//这里的runAsync是fetch的一个
//runAsyncParams是runAsync所有参数的数组
//serviceReselve servcie的resolve
//serviceError service的error
type fetchOptions = {
    manual:false,
    ...options,
};
function plugin(fetchInstance/*fetch的实例化*/,fetchOptions){
    ...省略
    return {
        onBefore(runAsyncParams) => {
            stopNow:boolean//如果为true,runAsync返回一个空结果的Promise
            returnNow:boolean//如果为true,runAsync返回fetch中的state
            ...rest
        } 
        //这些所有的参数都会覆盖fetch里的相同state
        onRequest(service,runAsyncParams) => {
            servicePromise?:Promise //这个如果存在会替代service往下运行
        }
        onSuccess(serviceReselve,runAsyncParams) => void
        onError(serviceError,runAsyncParams) => void
        onFinally(runAsyncParams,serviceReselve,serviceError)=> void 
        onMutate(data)=> void//这个是负责改变内部data的函数
    }
}
plugin.onInit = (fetchOptions) => (返回值存入data中,但内部数据后续可能会被覆盖);//实例化前运行

好好记住这些useRequest提供给plugin的接口,在实现uesRequest中才知道才能更好的知道这些代码的意图。

useRequest插槽在启动前都会运行一遍plugin的onInit

这里补充plugin.onInit,plugin.onInit函数运行在plugin函数的运行前面

useRequest代码解析

function useRequest<TData, TParams extends any[]>(
  service: Service<TData, TParams>,
  options?: Options<TData, TParams>,
  plugins?: Plugin<TData, TParams>[],
) {
  //useRequestImplement的params声明和useRequest完成一样
  return useRequestImplement<TData, TParams>(service, options, [
    ...(plugins || []),
    useDebouncePlugin,
    useLoadingDelayPlugin,
    usePollingPlugin,
    useRefreshOnWindowFocusPlugin,
    useThrottlePlugin,
    useAutoRunPlugin,
    useCachePlugin,
    useRetryPlugin,
  ] as Plugin<TData, TParams>[]);
}

useRequestImplement

function useRequestImplement<TData, TParams extends any[]>(
  service: Service<TData, TParams>,
  options: Options<TData, TParams> = {},
  plugins: Plugin<TData, TParams>[] = [],
) {
    //manual决定了是否运行后马上执行请求
  const { manual = false, ...rest } = options;
  const fetchOptions = {
    manual,
    ...rest,
  };
  const serviceRef = useLatest(service);

  const update = useUpdate();//负责更新如useRef中在更新时,仍旧保留属性不被重置的数据
  //useCreation是ahook提供给用户的接口,官网将的肯定比我清楚和清晰
  const fetchInstance = useCreation(() => {
    const initState = plugins.map((p) => p?.onInit?.(fetchOptions)).filter(Boolean);

    return new Fetch<TData, TParams>(
      serviceRef,
      fetchOptions,
      update,
      Object.assign({}, ...initState),
    );
  }, []);
  
  fetchInstance.options = fetchOptions;
  // run all plugins hooks
  fetchInstance.pluginImpls = plugins.map((p) => p(fetchInstance, fetchOptions));

  useMount(() => {
    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>;
}

export default useRequestImplement;
function useRequestImplement<TData, TParams extends any[]>(
  service: Service<TData, TParams>,
  options: Options<TData, TParams> = {},
  plugins: Plugin<TData, TParams>[] = [],
)

整个核心是fetch函数与plugin之间的交互,我用文字表达一下运行流程,看上面没问题,下面可以不看。

  1. 创建fetchOptions,除了options里的manual赋予默认值false以外属相全是引用
  2. 创建serviceRef 这个函数是最后一次传进来的service函数的引用
  3. 创建initState 运行所有plugin中的onInit(fetchOptions).filter(Boolean)属性函数
  4. 创建fetchInstance 引用实例化Fetch,这个fetchInstance永远指向第一次实例化的Fetch
  5. fetchInstance.options = fetchOptions
  6. fetchInstance.pluginImpls = (各插槽运行后的返回值)
  7. 挂载时,判断manual是否为true,来执行fetchInastance.run(fetchInstance.state.params || options.defaultParams || []);
  8. 卸载时,运行fetchInstance.cancel()