1. 找到源码
1.1 进入官网
1.2 从官网到 Github
1.3 github1s
使用 github1s 查看源码
在 url 中的 github 后面加上一个 1s 然后回车
1.4 ctrl + p
使用
ctrl + p快速定位到 useRequest hook
2. useRequest 的整体架构
2.1 plugin
- 定位到 useRequest 目录以后可以看到,我们使用的
useRequesthook 来源于src/useRequest.ts文件 - 在
useRequest.ts文件的顶部导入了很多的use...Plugin方法(或者也可以说是 hooks)。这些 plugin 的命名与 useRequest 所提供的功能是具有对应关系的(具体有哪些功能可以查看官网)。这就是useRequesthook 架构中的第一个重点 —— 将功能拆分成 plugin,实现代码的解耦,方便功能的插拔和扩展
2.2 生命周期钩子
- 我们可以大概的看一下每个 plugin 的内部结构,会发现所有的 plugin 方法都会返回一个对象,这个对象的属性可能是
onBefore、onRequest、onSuccess、onError、onFinally、onCancel、onMutate中的一个或多个。这就是useRequesthook 架构中的第二个重点 —— 基于生命周期的执行流程
2.3 Fetch 对象
- 继续往下看,
useRequesthook 的内部只调用了useRequestImplement方法,并且将所有的 plugin 都放在一个数组中传递进去 - 进入
useRequestImplement方法,在其顶部引用了很多 ahooks 中的其他 hook,这些 hook 的分析可以查看我的另外一篇博客。useMount就是在useEffect中直接执行一个函数,useUnmount就是在useEffect的返回值中执行
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);
}
});
- 上面这段代码的意思就是,如果没有设置
manual选项,那么useRequest会帮我们自动发一次请求。发请求时执行的是fetchInstance的run方法。那我们将重点放到fetchInstance这个对象是怎么生成的
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对象是通过实例化Fetch对象得来的。这就是useRequesthook 的第三个重点 —— useRequest 的执行核心 Fetch 对象 。我们所调用的run、runAsync、refresh、cancel等方法都来源于 Fetch 对象
2.4 小总结
- 总结一下,
useRequest的整体架构就是 —— 将功能拆分成单独的 plugin;调用 plugin 后返回生命周期钩子函数;在 Fetch 实例对象内部(重点是 runAsync 方法)调用相应的生命周期钩子从而实现相应的功能。
3. 发送第一个请求
在了解了
useRequest的整体架构以后,让我们来看看它是怎么样帮我们发送一个网络请求的
3.1 深入 useRequestImplement
1. 执行 plugin 身上的 onInit() 方法
- 在创建
fetchInstance对象时,会首先遍历所有的 plugin。如果 plugin 身上有onInit()方法,就会去执行它并且将所有的执行结果放到一个数组中,最后返回数组项为真值的元素
const initState = plugins.map((p) => p?.onInit?.(fetchOptions)).filter(Boolean);
- 就目前所有的 plugin 来看,只有
useAutoRunPlugin身上具有onInit方法 - 该方法接收
ready和manual作为参数,返回一个属性为loading,值为布尔值的对象 - 所以
initState的值为[{ loading: true / false }]
2. 实例化 Fetch 对象
- 接着往下看就是通过
new操作符调用Fetch类,得到fetchInstance对象
3. 执行所有的 plugin
fetchInstance.pluginImpls = plugins.map((p) => p(fetchInstance, fetchOptions));
- 在这里会去执行所有的 plugin 函数,并将所有的返回值放到一个数组中,最终赋值给
fetchInstance对象的pluginImpls属性
4. 执行 run() 方法
- 如果在使用
useRequest方法时,没有设置manual选项,那么会自动调用fetchInstance.run()方法 - 接下来就是将重点放到
Fetch类中,看一看在调用run()方法时都做了什么事
3.2 Fetch 类
1. run() 和 runAsync() 的区别
思考一个问题:不管以什么形式调用
run()方法,都不会拿到任何的返回值。但是如果在 Async 函数中调用runAsync()方法时,就可以拿到请求的返回值。这是怎么实现的呢?
run(...params: TParams) {
this.runAsync(...params).catch((error) => {
if (!this.options.onError) {
console.error(error);
}
});
}
- 在调用
run()时,其内部也是调用的runAsync()方法,那我们看一下runAsync()的大概结构
async runAsync(...params: TParams): Promise<TData> {
// *** 省略部分代码 ***
try {
const res = await servicePromise;
returen res;
} cache (error) {
// *** 省略部分代码 ***
return error
}
}
- 由于
runAsync()是一个 Async 函数,其返回值必然是一个 Promise 对象 - 但是在
run()方法中并没有使用.then()方法用来接收执行成功的结果,所以我们在调用run()方法时,永远也不会得到返回值。但是在使用 Async 函数调用runAsync()时是可以直接拿到请求结果的
2. runAsync()
// runAsync 中和生命周期钩子函数相关的主要代码,不是完整的源码
async runAsync(...params: TParams): Promise<TData> {
// 调用内部的 onBefore 钩子
const {
stopNow = false,
returnNow = false,
...state
} = this.runPluginHandler('onBefore', params);
try {
// 调用内部的 onRequest 钩子
let { servicePromise } = this.runPluginHandler('onRequest', this.serviceRef.current, params);
// 调用用户自定义的 onSuccess 钩子
this.options.onSuccess?.(res, params);
// 调用内部的 onSuccess 钩子
this.runPluginHandler('onSuccess', res, params);
// 调用用户自定义的 onFinally 钩子
this.options.onFinally?.(params, res, undefined);
} catch (error) {
// 调用用户自定义的 onError 钩子
this.options.onError?.(error, params);
// 调用内部的 onError 钩子
this.runPluginHandler('onError', error, params);
// 调用用户自定义的 onFinally 钩子
this.options.onFinally?.(params, undefined, error);
if (currentCount === this.count) {
// 调用内部的 onFinally 钩子
this.runPluginHandler('onFinally', params, undefined, error);
}
}
}
- 通过上面的代码可以发现,在
runAsync()中会去调用源码内部的onBefore、onRequest、onSuccess、onError 以及 onFinally生命周期钩子。同时也会去调用用户自己定义的onSuccess 和 onError生命周期函数
3. ready 选项
在使用
useRequest时,如果设置了ready选项的值为 false,请求永远不会发出。这是怎么实现的呢?
- 通过上面的分析,可以很清晰的看到,在
runAsync()内部其实就是在依次调用内部和用户自定义的生命周期钩子函数 useAutoRunPlugin执行完成以后会返回一个onBefore钩子
const useAutoRunPlugin: Plugin<any, any[]> = () => {
// *** 省略部分代码 ***
return {
onBefore: () => {
if (!ready) {
return {
stopNow: true,
};
}
},
};
}
- 在发送请求之前会去执行所有的
onBefore,如果ready选项为 false 时,runAsync()函数就会立即返回
async runAsync(...params: TParams): Promise<TData> {
const {
stopNow = false,
returnNow = false,
...state
} = this.runPluginHandler('onBefore', params);
// stop request
if (stopNow) {
return new Promise(() => {});
}
}
4. 发送请求
- 其中的
onRequest就是实际去发送请求,只有useCachePlugin的返回值中有该方法 - 单从发送请求的角度来看,这个执行流程比较简单,就是简单的函数调用
useRequest的所有功能都是在执行生命周期函数的过程中一点一点扩展的,后面的文章将深入的看看这些功能是如何实现的