ahooks中的request的基本功能实现原理

972 阅读3分钟

ahooks中的useRequest源码学习

在使用ahooks的时候,发现有些hooks确实可以减少工作量,于是抱着好奇的心态,查看和学习一下内部的实现。本文将是一个系列文章,来讲述不同的hooks的内部实现。这篇主要是讲useRequest的基本用法的实现。

基本用法

  1. 支持手动、自动的发送请求manual
  2. 支持刷新和取消请求
  3. 返回数据、处理请求错误和loading状态的返回

语法

包含2个参数,server: 是接受api请求,返回一个promise, options: 一些额外的选项

useRequest(server , options)

基本实现

当我们调用useRequest()的时候,查看源码可知,我们会执行到useRequestImplement, 我们先不管它的插件机制。

function useRequest(
  service,
  options
) {
  return useRequestImplement(service, options);
}

然后我们定位到useRequestImplement的实现,真正的返回是在就是在这个函数中执行的。

useRequestImplement的实现

useRequestImplement主要是分几个部分, 主要是实例化请求的控制(自动发送、取消等),使用了几个ahooks实现的其他hooks.

  1. 实例化请求的控制Fetch
  2. 根据manual判断是否初始化自动执行
function useRequestImplement(
  service,
  options = {},
) {
  const { manual = false, ...rest } = options;
  const fetchOptions = {
    manual,
    ...rest,
  };
  
  // 保持回调每一次获取的service都是最新的请求
  const serviceRef = useLatest(service);
   
  // 更新组件
  const update = useUpdate();
  
  // 使用useCreation 缓存Fetch创建的实例,不需要每次渲染都创建
  const fetchInstance = useCreation(() => {
    return new Fetch(
      serviceRef,
      fetchOptions,
      update
    );
  }, []);
  fetchInstance.options = 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)),
  }
}

Fetch的实现

主要的逻辑都是通过Fetch实现的,我们要带着一些问题去看看实现

  1. runrunAsync的主要区别是什么
  2. useRequest是怎么取消请求的
  3. refreshrefreshAsync是怎么重新调用上一次的请求的

Fetch文件来看,主要是定义了一个类,然后实现了一些对请求的操作。

实例的run()方法

从代码上查看得知,run方法主要是执行了runAsync, 只是不将错误往外层抛了

  run(...params) {
    this.runAsync(...params).catch((error) => {
      if (!this.options.onError) {
        console.error(error);
      }
    });
  }
实例的runAsync()方法

runAsync返回一个Promise, 主要是通过count来判断请求的处理, 执行一次count+1

class Fetch {
  async runAsync() {
      this.count += 1;
      const currentCount = this.count;
      try {
        const res = await servicePromise;
        if (currentCount !== this.count) {
              // prevent run.then when request is canceled
          return new Promise(() => {});
        }
        this.setState({
          data: res,
          error: undefined,
          loading: false,
        });
      } catch(e) {
        if (currentCount !== this.count) {
          // prevent run.then when request is canceled
          return new Promise(() => {});
        }
        this.setState({
          error,
          loading: false,
        });
      }
  }
}
实例的cancel()方法

useRequest()是如何取消请求的呢? 再看源码之前,我以为请求被直接取消了,但是发现并没有,请求还是发送了,只是不返回请求的返回值。

class Fetch {
    cancel() {
      this.count += 1;
      this.setState({
        loading: false,
      });
    }
}

本质上当我们执行cancel的时候,只是执行了将实例的count的加一,所以当我们请求返回之前,runAsync()还是在等待请求返回,此时如果我们修改了this.count的话, 导致它的 currentCountthis.count的值不等,所以就会直接返回一个空对象。如果发生了错误也不会被抛错,所以就相当于取消了请求的。

    const res = await servicePromise;
    if (currentCount !== this.count) {
          // prevent run.then when request is canceled
      return new Promise(() => {});
    }
refresh是如何重新发送请求

本质上就是调用了run()函数,那么参数来自于哪里呢?在调用runAsync()的时候保存了请求的参数到state中

    class Fetch {
        refresh() {
          this.run(...(this.state.params || []));
        }
    }

总结

useRequest()的基本功能中的取消请求本质上请求是发送出去了,但是请求数据并没有返回,错误也不会抛出,内部维护了一个count变量,根据请求发出当时的currentcountthis.count进行判断。