我为什么要搓一个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,体感不错,过程中暴露不少代码问题

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

参考

相关推荐
耶啵奶膘41 分钟前
uniapp-是否删除
linux·前端·uni-app
王哈哈^_^2 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
cs_dn_Jie3 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic3 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿4 小时前
webWorker基本用法
前端·javascript·vue.js
cy玩具4 小时前
点击评论详情,跳到评论页面,携带对象参数写法:
前端
qq_390161775 小时前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test5 小时前
js下载excel示例demo
前端·javascript·excel
Yaml45 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事5 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro