useRequest是一个异步数据管理的hooks,是ahooks Hooks库的核心hook,因为其通过插件式组织代码,大部分功能都通过插件的形式来实现,所以其核心代码行数较少,简单易懂,还可以支持我们自定义扩展功能。可以说,useRequest能处理React项目绝大多数的网络请求场景。
让咱自己写可能写不出来,那就先从模仿开始,通过阅读useRequest的代码,从中学习大佬们的代码逻辑和思维处理。
ahooks: https://ahooks.js.org/
文中代码基于3.7.2版本
在useRequest的源码实现中使用到了一些其他的hooks
const { data, error, loading, cancel } = useRequest(service);
useRequest通过定义一个class类Fetch 来维护相关的数据(data,loading等)和方法(run, refresh等)然后在useRequestImplement中创建Fetch实例,并返回实例属性和方法。
我对代码进行了拆分,保留了useRequest中核心的功能,该hook接收一个promise,需要返回data、error、loading、cancel状态。
const useRequestImplement = (service: Promise<any>) => {
const serviceRef = useLatest(service);
const update = useUpdate();
const fetchInstance = useCreation(() => {
return new Fetch(serviceRef, update);
}, []);
useMount(() => {
// useCachePlugin can set fetchInstance.state.params from cache when init
const params = fetchInstance.state.params ?? [];
// @ts-ignore
fetchInstance.runAsync(...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)),
};
};
export default class Fetch<TData, TParams extends any[]> {
public count: number = 0;
public state = {
loading: false,
params: undefined,
data: undefined,
error: undefined,
};
constructor(public serviceRef, public subscribe) {}
setState(s) {
this.state = {
...this.state,
...s,
};
this.subscribe();
}
async runAsync(...params: TParams): Promise<any> {
this.count += 1;
const currentCount = this.count;
this.setState({
loading: true,
params,
});
try {
const servicePromise = this.serviceRef.current(...params);
const res = await servicePromise;
if (currentCount !== this.count) {
return new Promise(() => {});
}
this.setState({
data: res,
error: undefined,
loading: false,
});
return res;
} catch (error) {
if (currentCount !== this.count) {
// prevent run.then when request is canceled
return new Promise(() => {});
}
this.setState({
error,
loading: false,
});
throw error;
}
}
cancel() {
this.count += 1;
this.setState({
loading: false,
});
}
refreshAsync() {
// @ts-ignore
return this.runAsync(...(this.state.params || []));
}
}
接收用户的自定义配置,包括manual(手动模式)和一些回调函数(onBefore,onSuccess, onError,onFinally)
const useRequestImplement = (service: Promise<any>, options) => {
const { manual = false, ...rest } = options;
const fetchOptions = {
manual,
...rest,
};
// const serviceRef = useLatest(service);
// const update = useUpdate();
// const fetchInstance = useCreation(() => {
return new Fetch(serviceRef, fetchOptions, update);
// }, []);
fetchInstance.options = fetchOptions;
useMount(() => {
if (!manual) {
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)),
};
};
export default class Fetch<TData, TParams extends any[]> {
// public count: number = 0;
//
// public state = {
// loading: false,
// params: undefined,
// data: undefined,
// error: undefined,
// };
constructor(public serviceRef, public options, public subscribe) {
this.state = {
...this.state,
loading: !options.manual,
};
}
// setState(s) {
// this.state = {
// ...this.state,
// ...s,
// };
// this.subscribe();
// }
// async runAsync(...params: TParams): Promise {
// this.count += 1;
// const currentCount = this.count;
//
// this.setState({
// loading: true,
// params,
// });
//
this.options.onBefore?.(params);
// try {
// const servicePromise = this.serviceRef.current(...params);
// const res = await servicePromise;
// if (currentCount !== this.count) {
// return new Promise(() => {});
// }
// this.setState({
// data: res,
// error: undefined,
// loading: false,
// });
//
this.options.onSuccess?.(res, params);
this.options.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.options.onFinally?.(params, undefined, error);
// throw error;
// }
// }
// cancel() {
// this.count += 1;
// this.setState({
// loading: false,
// });
// }
//
// refreshAsync() {
// // @ts-ignore
// return this.runAsync(...(this.state.params || []));
// }
run(...params: TParams) {
this.runAsync(...params).catch((error) => {
if (!this.options.onError) {
console.error(error);
}
});
}
refresh() {
// @ts-ignore
this.run(...(this.state.params || []));
}
mutate(data?: TData | ((oldData?: TData) => TData | undefined)) {
const targetData = isFunction(data) ? data(this.state.data) : data;
this.setState({
data: targetData,
});
}
}
runAsync方法返回一个promise,使用runAsync时,当请求报错会中断后续操作,需要手动捕获异常。
run方法则对runAsync进行了封装,帮助我们了捕获异常,或可以通过options.onError来处理异常行为。
useRequest维护了一份params,调用run()和runAsync()的时候会同时更新params。以便给refresh和refreshAsync方法使用
useRequest维护了一个count。
而runAsync方法本身也维护一个currentCount。
每次调用runAsync时,count进行一次++操作,然后将其赋值给currentCount。
每次cancel方法count会再进行一次++操作。通过比较count和currentCount的值来判断用户是否进行了取消操作,进行相应的处理
支持立即修改 useRequest 返回的 data 参数。
mutate 的用法与 React.setState 一致,支持 mutate(newData) 和 mutate((oldData) => newData) 两种写法。
以上是useRequest hook 的基本功能,剩余功能如loading状态延时、请求防抖、节流、数据缓存等功能都是通过插件的形式进行实现的,具体实现可以看 ahooks中的核心hook-useRequest(下)