我为什么要搓一个useRequest

背景

  • 在日常开发网络请求过程中,为了维护loading和error状态开发大量重复代码

  • 对于竞态问题,要么不处理,要么每个需要请求的地方都要写重复逻辑

  • 图表接口数据量大,甚至单接口响应就足以达到数十兆字节,而一个页面有数十个这样的请求,响应时间长,需要能够取消网络请求

以上逻辑,每个人的解法各不相同。为了解决上述问题,统一处理逻辑,需要一个能够统一管理网络请求状态的工具。

调研

首先想到的当然不是自己搓轮子,而是在社区上寻找是否已有解决方案。果不其然,找到了一些方案。

对于React,有像react-query这样的老前辈,功能全面,大而重;有像SWR这样的中流砥柱,受到社区广泛追捧;有像ahooks的useRequest这样的小清新,功能够用,小而美。

而对于Vue,一开始还真没让我找到类似的解决方案,后续进一步查找,发现有一个外国哥们仿造react-qeury仿写了一个vue-query,同时了解到雷达团队正是用的这一套解决方案,便又更深入了解了一下,发现这个库已经不维护了......进而了解到@tanstack/query,好家伙,这玩意胃口大得把react-query和vue-query都吃进去了,甚至svelte也不放过。继续找,发现有个哥们写了一个vue-request库,差不多类似于ahooks的useRequest,不错。然后经典的vue-use库也看了下,有一个useFetch方法,比较鸡肋,只适用于Fetch请求。

上述的社区库都相当不错,但对于我来说都太重了,功能繁多,而且在使用上,几个query都需要花费大量心智在缓存key上,太难用了。而ahooks和vue-request提供的useRequest的高阶函数,是比较符合我的胃口的,但是我还是嫌他们功能太多了。最关键的是,上述所有方案都没有达到我最主要的目的,能够真正取消网络请求。

因此,自己动手,丰衣足食。

动手

说干就干,搓一个咱自己的useRequest。

首先,定义useRequest的接口:

typescript 复制代码
export declare const useRequest: <P extends unknown[], R>(request: (signal: AbortSignal, ...args: P) => Promise<R>, options?: IUseRequestOptions<R> | undefined) => {
    result: ShallowRef<R | null>;
    loading: ShallowRef<boolean>;
    error: ShallowRef<Error | null>;
    run: (...args: P) => Promise<Error | R>;
    forceRun: (...args: P) => Promise<Error | R>;
    cancel: () => boolean;
};

然后定义三个响应式状态,这里之所以用shallowRef,是考虑到部分请求结果可能很深,如果用ref会导致性能很差。

ini 复制代码
const result = shallowRef<IResult | null>(null);
const loading = shallowRef(false);
const error = shallowRef<Error | null>(null);

定义普通变量,在useRequest内部使用,不要在内部实现读取响应式变量(PS:踩过坑了,有个页面用watchEffect,loading状态一变就发请求,导致无线循环):

ini 复制代码
let abortController = new AbortController();
let isFetching = false;

然后定义run函数,如果有进行中的请求就取消掉:

vbnet 复制代码
const run = async (...args: IParams) => {
  if (mergedOptions.cancelLastRequest && isFetching) {
    cancel();
  }
  abortController = new AbortController();

  setLoadingState(true);

  const res = await runRequest(...args);
  return res;
};

const runRequest = async (...args: IParams) => {
  const currentAbortController = abortController;
  try {
    const res = await request(currentAbortController.signal, ...args);
    if (currentAbortController.signal.aborted) {
      return new Error('canceled');
    }
    handleSuccess(res);
    return res;
  } catch (error) {
    if (currentAbortController.signal.aborted) {
      return new Error('canceled');
    }
    handleError(error as Error);
    return error as Error;
  }
};

另外暴露出cancel方法:

kotlin 复制代码
const cancel = () => {
  if (isFetching) {
    mergedOptions.onCancel?.();
    setLoadingState(false);
    abortController.abort('cancel request');
    return true;
  }

  return false;
};

在组件卸载时也取消掉未完成的请求:

scss 复制代码
onScopeDispose(() => {
  if (mergedOptions.cancelOnDispose && isFetching) {
    cancel();
  }
});

以上,就是最基础版的useRequest实现,想要了解更多,欢迎直接阅读useRequest源码,核心代码一共也就一百来行。看完再把star一点,诶嘿,美滋滋。

产出

收益

业务贡献

  • 提供响应式的result、loading、error状态

  • 内置缓存逻辑

  • 内置错误重试逻辑

  • 内置竞态处理逻辑

  • 兼容 Vue 2 & 3

  • 兼容 Axios & Fetch

  • 取消网络请求

个人成长

  • 学会如何编写一个基本的Vue工具库

  • 了解如何用vite打包,并且带上类型文件

  • 学会如何使用vue-demi兼容Vue2 & Vue3

  • 学会如何用VuePress编写文档,过程中没少看源码

  • 学会如何在npm上发包并维护

  • 之前用jest写过测试,这次尝试了一下vitest,体感不错,过程中暴露不少代码问题

  • 通过这个项目将以往所学的部分知识串联起来

参考

相关推荐
ice___Cpu13 分钟前
Linux 基本使用和 web 程序部署 ( 8000 字 Linux 入门 )
linux·运维·前端
JYbill16 分钟前
nestjs使用ESM模块化
前端
加油吧x青年34 分钟前
Web端开启直播技术方案分享
前端·webrtc·直播
吕彬-前端1 小时前
使用vite+react+ts+Ant Design开发后台管理项目(二)
前端·react.js·前端框架
小白小白从不日白1 小时前
react hooks--useCallback
前端·react.js·前端框架
恩婧2 小时前
React项目中使用发布订阅模式
前端·react.js·前端框架·发布订阅模式
mez_Blog2 小时前
个人小结(2.0)
前端·javascript·vue.js·学习·typescript
珊珊而川2 小时前
【浏览器面试真题】sessionStorage和localStorage
前端·javascript·面试
森叶2 小时前
Electron 安装包 asar 解压定位问题实战
前端·javascript·electron
drebander2 小时前
ubuntu 安装 chrome 及 版本匹配的 chromedriver
前端·chrome