ahooks中的useRequest源码学习
在使用ahooks的时候,发现有些hooks确实可以减少工作量,于是抱着好奇的心态,查看和学习一下内部的实现。本文将是一个系列文章,来讲述不同的hooks的内部实现。这篇主要是讲useRequest的基本用法的实现。
基本用法
- 支持手动、自动的发送请求
manual - 支持刷新和取消请求
- 返回数据、处理请求错误和loading状态的返回
语法
包含2个参数,server: 是接受api请求,返回一个promise, options: 一些额外的选项
useRequest(server , options)
基本实现
当我们调用useRequest()的时候,查看源码可知,我们会执行到useRequestImplement, 我们先不管它的插件机制。
function useRequest(
service,
options
) {
return useRequestImplement(service, options);
}
然后我们定位到useRequestImplement的实现,真正的返回是在就是在这个函数中执行的。
useRequestImplement的实现
useRequestImplement主要是分几个部分, 主要是实例化请求的控制(自动发送、取消等),使用了几个ahooks实现的其他hooks.
- 实例化请求的控制
Fetch - 根据
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实现的,我们要带着一些问题去看看实现
run和runAsync的主要区别是什么useRequest是怎么取消请求的refresh和refreshAsync是怎么重新调用上一次的请求的
从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的话, 导致它的
currentCount 和 this.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变量,根据请求发出当时的currentcount和this.count进行判断。