我们在使用 react 开发前端应用时,不可避免地要进行数据请求,而在数据请求的前后都要执行很多的处理,例如
当请求比较多时,每次都要重复这样的操作。这里我们可以利用 react 提供的 hook,自己来封装一个 useRequest 。
明确 useRequest 的输入和输出。
要输入的数据:
要返回的数据:
1const useRequest = (url, data, config) => {
2 const [loading, setLoading] = useState(true);
3 const [result, setResult] = useState(null);
4 const [error, setError] = useState(null);
5
6 const request = async () => {
7 setLoading(true);
8 try {
9 const result = await axios({
10 url,
11 params: data,
12 method: "get",
13 });
14 if (result && result.status >= 200 && result.status <= 304) {
15 setResult(result.data);
16 } else {
17 setError(new Error("get data error in useRequest"));
18 }
19 } catch (reason) {
20 setError(reason);
21 }
22 setLoading(false);
23 };
24 useEffect(() => {
25 request();
26 }, []);
27
28 return {
29 loading,
30 result,
31 error,
32 };
33};在我们不考虑第三个配置 config 的情况下,这个 useRequest 就已经可以使用了。
1const App = () => {
2 const { loading, result, error } = useRequest(url);
3
4 return (
5 <div>
6 <p>loading: {loading}</p>
7 <p>{JSON.stringify(result)}</p>
8 </div>
9 );
10};我们在请求接口过程中,可能接口还没没有返回到数据,组件就已经被销毁了,因此我们还要添加上取消请求的操作,避免操作已经不存在的组件。关于 axios 如何取消请求,您可以查看之前写的一篇文章: axios 源码系列之如何取消请求
1const request = useCallback(() => {
2 setLoading(true);
3 const CancelToken = axios.CancelToken;
4 const source = CancelToken.source();
5
6 axios({
7 url,
8 params: data,
9 method: "get",
10 cancelToken: source.token, // 将token注入到请求中
11 })
12 .then((result) => {
13 setResult(result.data);
14 setLoading(false);
15 })
16 .catch((thrown) => {
17 // 只有在非取消的请求时,才调用setError和setLoading
18 // 否则会报组件已被卸载还会调用方法的错误
19 if (!axios.isCancel(thrown)) {
20 setError(thrown);
21 setLoading(false);
22 }
23 });
24
25 return source;
26}, [url, data]);
27
28useEffect(() => {
29 const source = request();
30 return () => source.cancel("Operation canceled by the user.");
31}, [request]);然后,接着我们就要考虑把配置加上了,在上面的调用中,只要调用了 useRequest,就会马上触发。但若在符合某种情况下才触发怎办呢(例如用户点击后才产生接口请求)? 这里我们就需要再加上一个配置。
1{
2 "manual": false, // 是否需要手动触发,若为false则立刻产生请求,若为true
3 "ready": false // 当manual为true时生效,为true时才产生请求
4}这里我们就要考虑 useRequest 中触发 reqeust 请求的条件了:
1const useRequest = () => {
2 // 其他代码保持不变
3 useEffect(() => {
4 if (!config || !config.manual || (config.manual && config.ready)) {
5 const source = request();
6 return () => source.cancel("Operation canceled by the user.");
7 }
8 }, [config]);
9};使用方法
1const App = () => {
2 const [ready, setReady] = useState(false);
3 const { loading, result, error } = useRequest(url, null, {
4 manual: true,
5 ready,
6 });
7
8 return (
9 <div>
10 <p>loading: {loading}</p>
11 <p>{JSON.stringify(result)}</p>
12 <button onClick={() => setReady(true)}>产生请求</button>
13 </div>
14 );
15};当然,实现手动触发的方式有很多,我在这里是用一个 config.ready 来触发,在 umi 框架中,是对外返回了一个 run 函数,然后执行 run 函数再来触发这个 useRequest 的执行。
1const useRequest = () => {
2 // 其他代码保持不变,并暂时忽略
3 const [ready, setReady] = useState(false);
4
5 const run = (r: boolean) => {
6 setReady(r);
7 };
8
9 useEffect(() => {
10 if (!config || !config.manual || (config.manual && ready)) {
11 if (loading) {
12 return;
13 }
14 setLoading(true);
15 const source = request();
16
17 return () => {
18 setLoading(false);
19 setReady(false);
20 source.cancel("Operation canceled by the user.");
21 };
22 }
23 }, [config, ready]);
24};