前言
终于到useRequest
了。useRequest
是一个用于发起和管理业务组件中的网络请求,提供了网络请求常用的state(如loading, data)管理,轮询,防抖,节流,错误重试,SWR等功能。 可以说是ahooks库最核心的hook了
架构
useRequest
采用插件化架构,大体可以拆分成核心模块
和插件系统
两个部分:
核心模块通过一个Fetch
类实现,主要用于发起请求,管理请求过程中产生的response,loading,error等数据。同时提供用于控制请求的方法: run
, runAsync
, cancel
, refresh
, mutate
等。核心模块还提供了请求各个阶段的生命周期配置项
插件系统是在核心模块的基础上扩展了hooks功能,例如轮询,防抖,节流,缓存等都是由插件提供,这样设计的目的在于解耦和易于扩展

源码目录
我们先来看useRequest
的源码目录:

- plugins: 用于存储各个扩展功能的插件
- utils: 工具函数
- Fetch: 核心模块
- types: 类型声明文件
- useRequest: 调用
useReuquestImplement
传入参数 - useReueqstImplement: 参数处理,生成Fetch实例,执行plugins等
- index: 入口文件
插件
前面说到useRequest
依赖它的插件机制来扩展功能,后续在分析源码时也绕不开这个机制,所以我们先来看它插件的类型声明:
typescript
export type Plugin<TData, TParams extends any[]> = {
(
fetchInstance: Fetch<TData, TParams>,
options: Options<TData, TParams>,
): PluginReturn<TData, TParams>;
onInit?: (options: Options<TData, TParams>) => Partial<FetchState<TData, TParams>>;
};
可以知道它的插件本质上是一个函数,接收两个参数:
- fetchInstance: 核心类的实例
- options: 调用hooks时传入的配置对象
返回值是一个PluginReturn
类型:
typescript
export interface PluginReturn<TData, TParams extends any[]> {
onBefore?: (params: TParams) =>
| ({
stopNow?: boolean;
returnNow?: boolean;
} & Partial<FetchState<TData, TParams>>)
| void;
onRequest?: (
service: Service<TData, TParams>,
params: TParams,
) => {
servicePromise?: Promise<TData>;
};
onSuccess?: (data: TData, params: TParams) => void;
onError?: (e: Error, params: TParams) => void;
onFinally?: (params: TParams, data?: TData, e?: Error) => void;
onCancel?: () => void;
onMutate?: (data: TData) => void;
}
可以看到就是一个存储了插件各种生命周期函数的对象,与hooks的生命周期函数对比,插件的生命周期函数可以用于内部控制请求流程,甚至重写请求方法,功能更强。
除了入参数和返回值,我们还可以看到函数本身挂载了一个onInit
方法,主要用于初始化状态
核心模块源码解析
核心模块是一个名为Fetch
的范型类,我们先来看这两个范型:
typescript
export default class Fetch<TData, TParams extends any[]> {}
- TData: 异步请求函数的返回值
- TParams: 异步请求函数需要的参数,这里
extends any[]
是将TParams约束成数组类型
这两个范型对应的参数会贯穿hooks整个请求流程,我们后面再讲
属性
接下来是这个类的属性:
typescript
export default class Fetch<TData, TParams extends any[]> {
pluginImpls: PluginReturn<TData, TParams>[];
count: number = 0;
state: FetchState<TData, TParams> = {
loading: false,
params: undefined,
data: undefined,
error: undefined,
};
constructor(
public serviceRef: RefObject<Service<TData, TParams>>,
public options: Options<TData, TParams>,
public subscribe: Subscribe,
public initState: Partial<FetchState<TData, TParams>> = {},
) {
this.state = {
...this.state,
loading: !options.manual,
...initState,
};
}
}
- pluginImpls:
PluginReturn
数组,存储着所有插件的生命周期 - count: 用于请求计数,防止出现竞态条件时,拿到旧的请求数据
- state: 存储请求各个阶段的state
- serviceRef: 调用hooks传入的请求函数
- options: 调用hooks传入的配置项
- subscribe: 用于触发React组件更新的函数
- initState: 存储plugin
onInit
函数执行后的返回值,这里取最终结果
construction
函数对state
属性进行了初始化,可以看到initState
里的参数会覆盖前面的
方法
ts
export default class Fetch<TData, TParams extends any[]> {
setState(s: Partial<FetchState<TData, TParams>> = {}) {}
runPluginHandler(event: keyof PluginReturn\<TData, TParams>, ...rest: any\[]){}
async runAsync(...params: TParams): Promise<TData> {}
run(...params: TParams) {}
cancel() {}
refresh() {}
refreshAsync() {}
mutate(data?: TData | ((oldData?: TData) => TData | undefined)) {}
}
- setState: 用于更新
state
- runPluginHandler: 用于执行插件的各个生命周期
- runAsync: 核心方法,包括发起请求,处理参数,调用插件和hook的生命周期等功能
- run:
runAsync
的同步版本 - cancel: 取消请求
- refresh: 用于重新发送请求,里面调用
run
方法,传入上一次请求的参数 - refreshAsync: 重新发送请求的异步版本,里面调用
runAsync
方法 - mutate: 直接修改
data
的方法
runAsync
我们先来看最核心的runAsync
方法,先是整体源码,下面是分步解析:
typescript
async runAsync(...params: TParams): Promise<TData> {
this.count += 1;
const currentCount = this.count;
const {
stopNow = false,
returnNow = false,
...state
} = this.runPluginHandler('onBefore', params);
// stop request
if (stopNow) {
return new Promise(() => {});
}
this.setState({
loading: true,
params,
...state,
});
// return now
if (returnNow) {
return Promise.resolve(state.data);
}
this.options.onBefore?.(params);
try {
// replace service
let { servicePromise } = this.runPluginHandler('onRequest', this.serviceRef.current, params);
if (!servicePromise) {
servicePromise = this.serviceRef.current(...params);
}
const res = await servicePromise;
if (currentCount !== this.count) {
// prevent run.then when request is canceled
return new Promise(() => {});
}
// const formattedResult = this.options.formatResultRef.current ? this.options.formatResultRef.current(res) : res;
this.setState({
data: res,
error: undefined,
loading: false,
});
this.options.onSuccess?.(res, params);
this.runPluginHandler('onSuccess', res, params);
this.options.onFinally?.(params, res, undefined);
if (currentCount === this.count) {
this.runPluginHandler('onFinally', params, res, undefined);
}
return res;
} catch (error) {
if (currentCount !== this.count) {
// prevent run.then when request is canceled
return new Promise(() => {});
}
this.setState({
error,
loading: false,
});
this.options.onError?.(error, params);
this.runPluginHandler('onError', error, params);
this.options.onFinally?.(params, undefined, error);
if (currentCount === this.count) {
this.runPluginHandler('onFinally', params, undefined, error);
}
throw error;
}
}
首先是将count
属性递增1,然后创建一个currentCount
变量存储当前的count
值,这个变量刚刚说了是为了解决出现竞态场景时返回的数据不一致的问题,具体用法我们接着往下看:
typescript
this.count += 1;
const currentCount = this.count;
接下来是调用plugin onBefore
生命周期方法,返回两个关键属性stopNow
和returnNow
,前者用于终止请求,返回一个pending Promise
;后者用于获取缓存的数据,返回一个resolve Promise
:
typescript
const {
stopNow = false,
returnNow = false,
...state
} = this.runPluginHandler('onBefore', params);
// stop request
if (stopNow) {
return new Promise(() => {});
}
this.setState({
loading: true,
params,
...state,
});
// return now
if (returnNow) {
return Promise.resolve(state.data);
}
发起请求
typescript
this.options.onBefore?.(params);
try {
// replace service
let { servicePromise } = this.runPluginHandler('onRequest', this.serviceRef.current, params);
if (!servicePromise) {
servicePromise = this.serviceRef.current(...params);
}
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,
});
this.options.onSuccess?.(res, params);
this.runPluginHandler('onSuccess', res, params);
this.options.onFinally?.(params, res, undefined);
if (currentCount === this.count) {
this.runPluginHandler('onFinally', params, res, undefined);
}
return res;
}
在发起请求前,先调用了hook的onBefore
生命周期函数
然后调用插件的onRequest
生命周期函数,返回一个请求后的Promise
可以看到这里做了一个判断,优先使用插件返回的Promise,如果没有,再调用传入的方法获取请求后的Promise
:
typescript
// replace service
let { servicePromise } = this.runPluginHandler('onRequest', this.serviceRef.current, params);
if (!servicePromise) {
servicePromise = this.serviceRef.current(...params);
}
const res = await servicePromise;
接下来就是判断count
值是否相等,若不相等,则返回一个pending Promise
:
typescript
if (currentCount !== this.count) {
// prevent run.then when request is canceled
return new Promise(() => {});
}
调用setState
将保存返回的data
调用hook的onSuccess
函数,插件的onSuccess
函数,和hook的onFinally
函数
这里还有一次count
相等的判断,若相等,则再调用插件的onFinally
函数
最后返回res:
typescript
this.setState({
data: res,
error: undefined,
loading: false,
});
this.options.onSuccess?.(res, params);
this.runPluginHandler('onSuccess', res, params);
this.options.onFinally?.(params, res, undefined);
if (currentCount === this.count) {
this.runPluginHandler('onFinally', params, res, undefined);
}
return res;
错误处理
最后我们来看下runAsync
的错误处理
首先仍然是count
状态的检查,如果不相等则返回一个pending Promise
,终止下面等操作
然后是存储error
,将loading
设置为false
分别调用hooks的onError
,插件的onError
,hooks的onFinally
和插件的onFinally
生命周期
最后抛出异常
typescript
catch (error) {
if (currentCount !== this.count) {
// prevent run.then when request is canceled
return new Promise(() => {});
}
this.setState({ error, loading: false, });
this.options.onError?.(error, params);
this.runPluginHandler('onError', error, params);
this.options.onFinally?.(params, undefined, error);
if (currentCount === this.count) {
this.runPluginHandler('onFinally', params, undefined, error);
}
throw error;
}
小结
runAsync
是Fetch
类的核心方法,内部实现了整个异步请求的生命周期管理,并通过count
的设计巧妙实现了竞态条件的控制
hooks的生命周期:
- onBefore
- onSuccess
- onError
- onFinally
插件的生命周期:
- onBefore
- onRequest
- onSuccess
- onError
- onFinally
- onCancel
- onMutate
下面我们来看其它几个方法的源码
setState
用于设置state
,并调用subscribe
方法触发组件的更新:
typescript
setState(s: Partial<FetchState<TData, TParams>> = {}) {
this.state = {
...this.state,
...s,
};
this.subscribe();
}
runPluginHandler
执行插件的各个生命周期,第一个参数用于指定生命周期类型,其余的是剩余参数:
typescript
runPluginHandler(event: keyof PluginReturn<TData, TParams>, ...rest: any[]) {
// @ts-ignore
const r = this.pluginImpls.map((i) => i[event]?.(...rest)).filter(Boolean);
return Object.assign({}, ...r);
}
run
runAsync
的同步版本,可以看到直接调用runAsync
方法,但没有返回值:
typescript
run(...params: TParams) {
this.runAsync(...params).catch((error) => {
if (!this.options.onError) {
console.error(error);
}
});
}
cancel
用于取消请求,这里可以看到它是通过改变count
的值来实现请求的取消,并不是真的将http请求给取消了:
typescript
cancel() {
this.count += 1;
this.setState({
loading: false,
});
this.runPluginHandler('onCancel');
}
refresh
重新请求的方法:
typescript
refresh() {
// @ts-ignore
this.run(...(this.state.params || []));
}
refreshAsync
重新请求方法的异步版本:
typescript
refreshAsync() {
// @ts-ignore
return this.runAsync(...(this.state.params || []));
}
mutate
直接改变data
的值:
typescript
mutate(data?: TData | ((oldData?: TData) => TData | undefined)) {
const targetData = isFunction(data) ? data(this.state.data) : data;
this.runPluginHandler('onMutate', targetData);
this.setState({
data: targetData,
});
}
整体源码
ts
/* eslint-disable @typescript-eslint/no-parameter-properties */
import type { RefObject } from 'react';
import { isFunction } from '../../utils';
import type { FetchState, Options, PluginReturn, Service, Subscribe } from './types';
export default class Fetch<TData, TParams extends any[]> {
pluginImpls: PluginReturn<TData, TParams>[];
count: number = 0;
state: FetchState<TData, TParams> = {
loading: false,
params: undefined,
data: undefined,
error: undefined,
};
constructor(
public serviceRef: RefObject<Service<TData, TParams>>,
public options: Options<TData, TParams>,
public subscribe: Subscribe,
public initState: Partial<FetchState<TData, TParams>> = {},
) {
this.state = {
...this.state,
loading: !options.manual,
...initState,
};
}
setState(s: Partial<FetchState<TData, TParams>> = {}) {
this.state = {
...this.state,
...s,
};
this.subscribe();
}
runPluginHandler(event: keyof PluginReturn<TData, TParams>, ...rest: any[]) {
// @ts-ignore
const r = this.pluginImpls.map((i) => i[event]?.(...rest)).filter(Boolean);
return Object.assign({}, ...r);
}
async runAsync(...params: TParams): Promise<TData> {
this.count += 1;
const currentCount = this.count;
const {
stopNow = false,
returnNow = false,
...state
} = this.runPluginHandler('onBefore', params);
// stop request
if (stopNow) {
return new Promise(() => {});
}
this.setState({
loading: true,
params,
...state,
});
// return now
if (returnNow) {
return Promise.resolve(state.data);
}
this.options.onBefore?.(params);
try {
// replace service
let { servicePromise } = this.runPluginHandler('onRequest', this.serviceRef.current, params);
if (!servicePromise) {
servicePromise = this.serviceRef.current(...params);
}
const res = await servicePromise;
if (currentCount !== this.count) {
// prevent run.then when request is canceled
return new Promise(() => {});
}
// const formattedResult = this.options.formatResultRef.current ? this.options.formatResultRef.current(res) : res;
this.setState({
data: res,
error: undefined,
loading: false,
});
this.options.onSuccess?.(res, params);
this.runPluginHandler('onSuccess', res, params);
this.options.onFinally?.(params, res, undefined);
if (currentCount === this.count) {
this.runPluginHandler('onFinally', params, res, undefined);
}
return res;
} catch (error) {
if (currentCount !== this.count) {
// prevent run.then when request is canceled
return new Promise(() => {});
}
this.setState({
error,
loading: false,
});
this.options.onError?.(error, params);
this.runPluginHandler('onError', error, params);
this.options.onFinally?.(params, undefined, error);
if (currentCount === this.count) {
this.runPluginHandler('onFinally', params, undefined, error);
}
throw error;
}
}
run(...params: TParams) {
this.runAsync(...params).catch((error) => {
if (!this.options.onError) {
console.error(error);
}
});
}
cancel() {
this.count += 1;
this.setState({
loading: false,
});
this.runPluginHandler('onCancel');
}
refresh() {
// @ts-ignore
this.run(...(this.state.params || []));
}
refreshAsync() {
// @ts-ignore
return this.runAsync(...(this.state.params || []));
}
mutate(data?: TData | ((oldData?: TData) => TData | undefined)) {
const targetData = isFunction(data) ? data(this.state.data) : data;
this.runPluginHandler('onMutate', targetData);
this.setState({
data: targetData,
});
}
}