使用 react 的 hook 实现一个 useRequest

蚊子前端博客
发布于 2021-02-20 10:14
如何使用hook实现一个useRequest呢?

我们在使用 react 开发前端应用时,不可避免地要进行数据请求,而在数据请求的前后都要执行很多的处理,例如

  1. 展示 loading 效果告诉用户后台正在处理请求;

  2. 若要写 async-await,每次都要 try-catch 进行包裹;

  3. 接口异常时要进行错误处理;

当请求比较多时,每次都要重复这样的操作。这里我们可以利用 react 提供的 hook,自己来封装一个useRequest

umi 框架中已经有实现了一个 useRequest 方法,useRequest-umi-hook,他这里实现的功能很多,我们只实现一个基本的功能,其他更多的功能,您可以自己拓展。

平平淡淡才是真-蚊子的前端博客

1. 搭建一个基本结构

在 useRequest 中,我们基于 axios 来封装,不过这里我们不会对外暴露太多的配置,首先我们来明确 useRequest 的输入和输出。

要输入的数据:

  • url:要请求的接口地址;

  • data:请求的数据(默认只有 get 方式);

  • config:其他一些配置,可选,如(manual?: boolean; // 是否需要手动触发);

要返回的数据:

  • loading:数据是否在请求中;

  • data:接口返回的数据;

  • error:接口异常时的错误;

2. 具体的实现

我们在确定输入和输出的格式后,就可以具体来实现了。

2.1 不带 config 配置的

首先定义好输入和输出的数据:

COPYJAVASCRIPT

const useRequest = (url, data, config) => { const [loading, setLoading] = useState(false); const [result, setResult] = useState(null); const [error, setError] = useState(null); return { loading, result, error, }; }; export default useRequest;

然后就可以添加数据请求了:

COPYJAVASCRIPT

const useRequest = (url, data, config) => { const [loading, setLoading] = useState(true); const [result, setResult] = useState(null); const [error, setError] = useState(null); const request = async () => { setLoading(true); try { const result = await axios({ url, params: data, method: 'get', }); if (result && result.status >= 200 && result.status <= 304) { setResult(result.data); } else { setError(new Error('get data error in useRequest')); } } catch (reason) { setError(reason); } setLoading(false); }; useEffect(() => { request(); }, []); return { loading, result, error, }; };

在我们不考虑第三个配置 config 的情况下,这个useRequest就已经可以使用了。

COPYJAVASCRIPT

const App = () => { const { loading, result, error } = useRequest(url); return ( <div> <p>loading: {loading}</p> <p>{JSON.stringify(result)}</p> </div> ); };

强颜欢笑-蚊子的前端博客

2.2 添加取消请求

我们在请求接口过程中,可能接口还没没有返回到数据,组件就已经被销毁了,因此我们还要添加上取消请求的操作,避免操作已经不存在的组件。关于 axios 如何取消请求,您可以查看之前写的一篇文章:axios 源码系列之如何取消请求

COPYJAVASCRIPT

const request = useCallback(() => { setLoading(true); const CancelToken = axios.CancelToken; const source = CancelToken.source(); axios({ url, params: data, method: 'get', cancelToken: source.token, // 将token注入到请求中 }) .then((result) => { setResult(result.data); setLoading(false); }) .catch((thrown) => { // 只有在非取消的请求时,才调用setError和setLoading // 否则会报组件已被卸载还会调用方法的错误 if (!axios.isCancel(thrown)) { setError(thrown); setLoading(false); } }); return source; }, [url, data]); useEffect(() => { const source = request(); return () => source.cancel('Operation canceled by the user.'); }, [request]);

2.3 带有 config 配置的

然后,接着我们就要考虑把配置加上了,在上面的调用中,只要调用了 useRequest,就会马上触发。但若在符合某种情况下才触发怎办呢(例如用户点击后才产生接口请求)? 这里我们就需要再加上一个配置。

COPYJSON

{ "manual": false, // 是否需要手动触发,若为false则立刻产生请求,若为true "ready": false // 当manual为true时生效,为true时才产生请求 }

这里我们就要考虑 useRequest 中触发 reqeust 请求的条件了:

  • 没有 config 配置,或者有 config,但 manual 为 false;

  • 有 config 配置,且 manual 和 ready 均为 true;

COPYJAVASCRIPT

const useRequest = () => { // 其他代码保持不变,并暂时忽略 useEffect(() => { if (!config || !config.manual || (config.manual && config.ready)) { const source = request(); return () => source.cancel('Operation canceled by the user.'); } }, [config]); };

使用方式:

COPYJAVASCRIPT

const App = () => { const [ready, setReady] = useState(false); const { loading, result, error } = useRequest(url, null, { manual: true, ready }); return ( <div> <p>loading: {loading}</p> <p>{JSON.stringify(result)}</p> <button onClick={() => setReady(true)}>产生请求</button> </div> ); };

当然,实现手动触发的方式有很多,我在这里是用一个config.ready来触发,在 umi 框架中,是对外返回了一个 run 函数,然后执行 run 函数再来触发这个 useRequest 的执行。

COPYJAVASCRIPT

const useRequest = () => { // 其他代码保持不变,并暂时忽略 const [ready, setReady] = useState(false); const run = (r: boolean) => { setReady(r); }; useEffect(() => { if (!config || !config.manual || (config.manual && ready)) { if (loading) { return; } setLoading(true); const source = request(); return () => { setLoading(false); setReady(false); source.cancel('Operation canceled by the user.'); }; } }, [config, ready]); };

3. 总结

我们在这里实现的useRequest,也只是实现几个基本功能!对于更大的业务来说,可能需求点更多,例如轮训,防抖等,这些功能就需要自己去添加啦!

手机没电了-蚊子的前端博客

标签:
阅读(4368)

公众号:

qrcode

微信公众号:前端小茶馆