使用 ahooks 中的 useRequest 轻松管理React中的网络请求

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

关于 ahooks

2022年的今天,在 React 中使用 Hook 已经是常规的不能再常规的操作了,我们会大量的通过组合 React 提供的 Hook,创建属于自己业务的专属自定义 Hook,亦或是各种工具 Hook。

阿里前端团队出品的 ahooks 正是这样一套 Hook 工具集,里面提供数十个常用的 Hook,可以极大的方便我们的日常开发。

今天我们要着重介绍的。就是 ahooks 项目中最为重量级的一个 hook ,useRequest

为什么用 useRequest

为什么要用 useRequest,其实要从状态说起。在过去项目开发中我们经常将 用户 UI 交互状态服务端状态 混为一谈,经常是 Redux 一把嗦,通过 fetch 获取到数据用,使用 useState 将数据作为状态,最终渲染到页面。

这样做看起来并没有什么问题,很多项目也都是这样写的。但是 用户 UI 交互状态 是实时性较强的,是我们作为前端可控的状态,而 服务端状态 则涉及到 http 请求,他是异步的、是不完全可控的。

例如一个网络请求其实不光只有最终的数据这一个状态,还有 loading、error等状态,可能还涉及到请求失败后的 retry,一些服务端状态变更不频繁、但是前端需要频繁调用的,还涉及到数据的状态缓存与共享。

仅仅是上面那我们说的这些就已经涉及了大量的状态管理,这样的状态管理显然是有别于 用户 UI 交互状态 的,因此 SWR、react-query、rtk-query这些专注于服务端状态管理的库应运而生.

如果你在之前接触过 SWR 或者是 react-query,那么你可能会比较好的理解 useRequest,你可以将它看作是一个轻量级的 react-query。

快速上手

我们写一个模拟请求用户信息的组件,我们用普通的方式写的话,大致是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 模拟异步请求
const fetchUserInfo = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const random = Math.random();
if (random > 0.5) {
resolve({
name: 'John',
time: new Date().toLocaleString()
});
} else {
reject('这是一个随机的 Error');
}
}, 1000);
})
}

function App() {
const [data, setData] = useState();
const request = async () => {
const resp = await fetchUserInfo();
setData(JSON.stringify(resp));
}
return (
<div className="App">
<button onClick={request}>Requst</button>
<div>{data}</div>
</div>
);
}

就像我们之前说的,这种普通的请求方式缺少 loadingerror等状态,我们只需要简单的修改代码就能通过 useRequest 实现。

1
2
3
4
5
6
7
8
9
10
11
function App() {
const { data, loading, error, run } = useRequest(fetchUserInfo);
return (
<div className="App">
<button onClick={run}>Requst</button>
{loading && <div>Loading...</div>}
{error && <div>Error: {error}</div>}
{data && <div>{JSON.stringify(data)}</div>}
</div>
);
}

如你所见,他真的非常简单,我们只需要传入一个 Promise ,剩下的全部交给 useRequest 就可以了!

useRequest 的第一个参数 service 是一个异步函数,在组件初次加载时,会自动触发该函数执行。同时自动管理该异步函数的 loading , data , error 等状态。

手动请求

useRequest 是自定触发请求的,也就说当组件挂载到 DOM 树上后,请求会立即发出。如果我们希望手动触发请求,可以为 useRequest 函数配置第二个参数。

第二参数是一个 options 配置,如果设置了 options.manual = true,则 useRequest 不会默认执行,需要通过 run 来触发执行。

1
2
3
const { data, loading, error, run } = useRequest(fetchUserInfo, {
manual: true
});

刷新

不同于 run 函数可以接收参数,refresh 不接受参数,它会自动的使用上一次调用传入的参数来发起请求。

1
2
3
const { data, loading, error, run, refresh} = useRequest(fetchUserInfo, {
manual: true
});

取消请求 cancel

一般来说,我们不需要处理取消请求,useRequest 会自动在组件卸载时取消请求。

1
const { data, loading, error, cancel} = useRequest(fetchUserInfo);

突变 mutate

突变这个概念在 SWR、react-query 中也是存在的,在 ahooks 中使用它非常简单,我们可以把它看成是一个 setData 函数,它可以修改由 useRequest 返回的 data 状态。

1
const { data,  mutate} = useRequest(fetchUserInfo);

它往往被用于乐观更新这样的场景!我会在后续文章中介绍如何使用 ahooks 的 useRequest 实现乐观更新。

options 可配置项简介

除了上面我们提到的 options.manual = true ,用于配置手动执行请求之外,options还有很多可选的配置,接下来我们简单介绍几个常用的配置。

1. 传递参数给 service

上面我们介绍了,参数1 service 一般是一个 结果值为 Promise 的函数,如果我们需要传参给这个函数应该怎么做?

答案还是 options,我们可以配置options.defaultParams 字段,如果只有一个参数,直接赋值即可,多参数则赋值一个元组即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function App() {
const { data, loading, error, run } = useRequest(fetchUserInfo, {
defaultParams: [
1, 'unknown'
]
});
return (
<div className="App">
<button onClick={() => run(123, 'man')}>Requst</button>
{loading && <div>Loading...</div>}
{error && <div>Error: {error}</div>}
{data && <div>{JSON.stringify(data)}</div>}
</div>
);
}

2. loading状态延时 loadingDelay

一般我们会通过{loading && <div>Loading...</div>} 这样的代码来展示一段加载中动画,但是有时候在网络良好的状况下这个 loading 状态的持续时间非常短暂,如果这个请求是比较频繁调用的,那么就会不断的闪烁 加载动画,这样的体验是很差的,我们可以通过 loadingDelay 配置当 loading时间超过这个值时才展示loading动画

1
2
3
4
const { data, loading, error, run } = useRequest(fetchUserInfo, {
defaultParams: 222,
loadingDelay: 300
});

3. 生命周期

useRequest 提供了多个生命周期回调函数:

  1. onBefore?: (params: TParams) => void; //在请求之前
  2. onSuccess?: (data: TData, params: TParams) => void; //请求成功
  3. onError?: (e: Error, params: TParams) => void; //请求失败
  4. onFinally?: (params: TParams, data?: TData, e?: Error) => void; //请求结束
1
2
3
4
5
6
7
const { data, loading, error, run, refresh } = useRequest(fetchUserInfo, {
defaultParams: 222,
loadingDelay: 300,
onSuccess: (data) => { console.log('请求成功了'); },
onError: (err) => { console.log('请求失败了'); },
onFinally: () => { console.log('请求结束了'); }
});

4. 轮询

我们只需要简单配置一个属性,就可以将请求设置为轮询模式。

1
2
3
const { data, loading, error} = useRequest(fetchUserInfo, {
pollingInterval: 3000,
});

我们还可以通过配置 pollingErrorRetryCount 字段,设置轮询错误重试次数。如果设置为 -1,则无限次,当轮询过程出错到达计数时,停止轮询。

配置 pollingWhenHidden 可以设置在页面隐藏时,是否继续轮询。如果设置为 false,在页面隐藏时会暂时停止轮询,页面重新显示时继续上次轮询。

如果设置 options.manual = true,则初始化不会启动轮询,需要通过 run/runAsync 触发开始。

5. ready

ready 的效果其实有点等同于 run 函数,每当 ready 变成 true 时,就会发起一次请求,调用参数1传入的 Promise 异步函数。需要注意的是有参数的情况,ready是用默认参数发起请求,即使后续多次 改变 ready 的状态,他也是使用我们设置的 options.defaultParams 来传递给 service 的。这一定一定要注意,他的调用效果相当于是拿着默认参数的 refresh!

它相当于一个提供了一个总开关,只有当该条件满足时才允许发送请求,无论时自动请求还是手动请求,都受到该字段限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function App() {
const [ready, { toggle }] = useBoolean(false);
const { data, loading, error, run, refresh } = useRequest(fetchUserInfo, {
defaultParams: 222,
ready,
onSuccess: (data) => { console.log('请求成功了'); },
onError: (err) => { console.log('请求失败了'); },
onFinally: () => { console.log('请求结束了'); }
});

return (
<div className="App">
<button onClick={() => run(Math.random(), 'man')}>Requst</button>
<button onClick={refresh}>refresh</button>
<button onClick={toggle}>toggle</button>
{loading && <div>Loading...</div>}
{error && <div>Error: {error}</div>}
{data && <div>{JSON.stringify(data)}</div>}
</div>
);
}

6. 依赖刷新

useRequest 提供了一个 options.refreshDeps 参数,当它的值变化后,会重新触发请求。它的效果基本等同于手写 useEffect。

例如:

1
2
3
4
const [id, setId] = useState(555);
const { data } = useRequest(() => fetchUserInfo(id), {
refreshDeps: [id],
});

总结

行文至此,想必你应经对如何使用 useRequest 有了一定的了解,在后续文章中,我会继续介绍 useRequest 中的其他高级用法,例如 swr 缓存、请求的防抖、节流,还有上面我们说的 乐观更新 等等。

PS:再本文中,大写的 SW R特指的由 Next.js 团队推出的 SWR库,小写的 swr 则指的是 stale-while-revalidate 这一概念,请注意区分。