https://ahooks.js.org/zh-CN/guide
https://github.com/umijs/umi-request
useRequest规范
请求层治理:
- 统一请求库,新建应用,不需要重复实现一套请求层逻辑
- 规范请求接口设计规范
- 统一接口文档,应用接口设计一致
- 减少开发者在接口设计、文档维护、请求层逻辑开发上花费的沟通和人力成本

useRequest特点
- 自动请求,data,loading
- 手动请求
- run,runAsync
- manual:true
- cancel 取消请求
- refreshDeps: [] 请求依赖
- 轮询 pollingInterval: 60000
- loadingDelay:300
- 防抖 debounceInterval
- 节流 throttleInterval
- 屏幕聚焦重新请求 refreshOnWindowFocus
- 错误重试 retryCount
- SWR(stale-while-revalidate),缓存
- cacheKey

useRequest 参考
https://www.yuque.com/mayiprototeam/gfyt69/ggqy8q
const {loading: boolean,data?: TData,error?: Error,params: TParams || [],run: (...params: TParams) => void,runAsync: (...params: TParams) => Promise<TData>,refresh: () => void,refreshAsync: () => Promise<TData>,// 通过 mutate,直接修改 datamutate: (data?: TData | ((oldData?: TData) => (TData | undefined))) => void,cancel: () => void,} = useRequest<TData, TParams>(service: (...args: TParams) => Promise<TData>,{manual?: boolean,defaultParams?: TParams,onBefore?: (params: TParams) => void,onSuccess?: (data: TData, params: TParams) => void,onError?: (e: Error, params: TParams) => void,onFinally?: (params: TParams, data?: TData, e?: Error) => void,});
run 轮询请求
manual = true ,即可阻止 useRequest 初始化执行。只有触发 run 时才会开始执行
polling 轮询文档 https://ahooks.js.org/zh-CN/hooks/use-request/polling
import { useRequest } from 'ahooks';import { message } from 'antd';function App() {const { data, loading, run, cancel } = useRequest(fetchUser, {manual: true, // 手动请求retryCount: 3, // 请求异常后,3 次重试;第一次重试等待 2s,第二次重试等待 4spollingInterval: 60000, // ms 毫秒,轮询模式,定时 1分钟触发一次pollingErrorRetryCount: 5, // 轮询错误重试次数pollingWhenHidden: false, // 页面隐藏时,暂时停止轮询;显示时继续上次轮询loadingDelay: 400, // ms 延迟 loading 变成 true 的时间,防止闪烁onError: (error) => {// 错误信息提示message.error(error.message);},});useEffect(() => {run(id); // run传参return () => {cancel() // cancel 停止轮询}}, [id])}export function fetchUser() {return fetch('/api/user')}
- retryCount场景:API 偶尔会在短时间内出现故障
- http请求偶尔返回异常,最好为您的请求实施重试机制
- loadingDealy 假如请求在 300ms 内返回,则 loading 不会变成 true,避免了页面展示 Loading… 的情况。
- debounceWait: 防抖等待时间
- debounceMaxWait: 允许被延迟的最大值
- debounceLeading: 在延迟开始前执行调用
- debounceTrailing: 在延迟结束后执行调用
- 事件之后再最后一次触发的300秒后执行,场景:输入,查询;因为输入一直再触发,所以要等输入完毕之后在做操作
manual 手动触发请求
options.manual = true,初始化不会启动轮询,要通过 run/runAsync 触发开始
- run (…params: TParams) => void
- 自动捕获异常,通过 onError函数捕获异常
- runAsync (…params: TParams) => Promise
- 返回 Promise的异步函数,需要自己捕获异常
- runAsync只有真正执行的时候,才会返回Promise,没有执行时,不会有任何返回
- cancel 可以中止正在等待执行的函数
options.ready参数,当其值为false时,请求永远都不会发出run/runAsync触发的请求都不会执行 ```tsx import { useRequest } from ‘@hooks’;
async function getUsername() { return Promise.resolve(‘jack’); }
export default () => { const { data, error, loading, run } = useRequest(getUsername, { manual: true, // 阻止初始化执行,只有触发 run 时才会开始执行。 })
if (error) return
return ( ) }
<a name="uoUNE"></a>### visibilitychange 标签页隐藏或显示事件浏览器标签页被隐藏或显示的时候,会触发 visibilitychange事件<br />[https://developer.mozilla.org/zh-CN/docs/Web/API/Document/visibilitychange_event](https://developer.mozilla.org/zh-CN/docs/Web/API/Document/visibilitychange_event)- document.visibilityState取值:hidden,visible;- document.hidden取值:true/false<a name="tysxy"></a>### visibilitychange场景- 监听返回、分享、跳转、切入后台;- 用于控制播放音乐视频、轮播等循环动画效果等;- 可用于统计某个页面用户停留时长,即显示时开始计算,隐藏时结束计算<a name="Em6tK"></a>### refreshDeps 依赖参数请求依赖改变,刷新请求```jsxconst { data, run, loading } = useRequest(() => getUserSchool(userId), {// 当数组内容变化后,发起请求,类似 useEffect的参数refreshDeps: [userId],debounceWait: 1000, // ms 频繁触发 run/runAsync,会以防抖策略进行请求,一秒// throttleWait: 1000, // 节流})// refresh 刷新const { data, refresh } = useRequest(() => getUserSchool(userId));useEffect(() => {refresh();}, [userId]);
自动请求
useRequest 集成了 umi-request
- useRequest的用法和 axios基本一样
- 安装了 ahooks,就直接使用 useRequest请求,不需要在安装 umi-request
- 如果第一个参数不是 Promise,会通过 umi-request 来发起网络请求 ```javascript // 初始化立即执行 const { data, error, loading } = useRequest(‘/api/userInfo’); const { data, error, loading } = useRequest(getUsername)
// 用法 2 const { data, error, loading } = useRequest({ url: ‘/api/changeUsername’, method: ‘post’, });
// 用法 3
const { data, error, loading, run } = useRequest(userId=> {
// userId 是 run执行时,传进来的
return /api/userInfo/${userId}
});
// 用法 4 const { loading, run } = useRequest((username) => ({ url: ‘/api/changeUsername’, method: ‘post’, data: { username }, }));
<a name="LfdOn"></a>### 并行请求 ❌也就是同一个接口,我们需要维护多个请求状态<br />ahooks 3.x 取消了并行请求<br />[https://github.com/alibaba/hooks/issues/1378](https://github.com/alibaba/hooks/issues/1378)<br />原因:- useRequest 实现并行能力复杂度太高- 建议将 请求和UI 封装成一个组件实现并行请求的能力。而不是把所有请求都放到父级;- 虚拟滚动场景,确实会有问题。这种场景建议自行管理请求状态<a name="dtotT"></a>### useRequest 生命周期```javascriptconst { data, loading, run, cancel } = useRequest(fetchUser, {manual: true, // 手动请求// 请求之前onBefore: () => { },// 请求成功触发onSuccess: (res) => { console.log(res); },// 请求完成onFinally: () => { },// 请求失败onError: (error) => {// 错误信息提示message.error(error.message);},});const { data, loading, run } = useRequest(data => ({// data是 run传进来的url: "http://127.0.0.1:8080/api/user",method: "POST",body: JSON.stringify(data),headers: {'Content-Type': 'application/json;charset=utf-8'}}), {manual: true,onSuccess: (result) => {if (result.status === 'ok') {message.success('流程已经提交');}}});
usePagination分页
基于 useRequest 实现,封装了常见的分页逻辑;page、pageSize、total 管理
https://ahooks.js.org/zh-CN/hooks/use-pagination
筛选条件变化,重置分页,重新发起网络请求
usePagination 分页请求,不支持手动触发、不支持轮询等
import React from 'react';import { usePagination } from 'ahooks';import { List } from 'antd';async function getUserList({current,pageSize,}): Promise<{ total: number; list: UserListItem[] }> {return fetch('/api/user', {current, pageSize})}export default () => {const { data, loading, pagination } = usePagination(getUserList);return (<Listloading={loading}dataSource={data}renderItem={(item) => (<List.Item><List.Item.Metaavatar={<Avatar src="https://joesch.moe/api/v1/random" />}title={<a href="https://ant.design">{item.title}</a>}description="Ant Design for background applications"/></List.Item>)}pagination={{position: 'bottom',align: 'right',current: pagination.current,pageSize: pagination.pageSize,onChange: pagination.onChange,onShowSizeChange: pagination.onChange,showQuickJumper: true,showSizeChanger: true,total: data.total}}/>);};
网络请求失败自动重试
SWR 缓存 & 预加载
swr,stale-while-revalidate 在发起网络请求时,会优先返回之前缓存的数据
stale-whie-revalidate是 HTTP RFC 5861 中描述的一种 Cache-Control 扩展,属于对缓存到期的控制
- options.cacheKey,将当前请求成功的数据缓存起来
- 下次组件初始话时,如果有缓存数据,会优先返回缓存数据,然后在背后发送新请求,先使用旧的,如果有新的数据,就会拿新的替换
- staleTime 数据保持新鲜时间
- 在这个时间内,认为数据时新鲜的,不会重新发送请求
- 设置为 -1,表示数据永远新鲜
- cacheTime 数据缓存时间
- 超过这个时间,会清空这条数据缓存,缓存的回收时间,默认5分钟后回收
- 设置为-1,表示缓存数据永远不会过期
import { useRequest,clearCache } from 'ahooks'const { data, loading, run, refresh, cancel } = useRequest(fetchUser, {cacheKey: 'cacheUser',staleTime: 60000 * 10, // msretryCount: 5, // 错误重试});
对于一些数据不是经常变化的接口,使用 SWR 后,可以极大提高用户使用体验
配置 cacheKey ,即可进入 SWR 模式
同一个 cacheyKey 的数据是全局共享的。通过这个特性,可以实现“预加载”功能;
- 当我们改变其中某个cacheKey的内容时,其他相同cacheKey的内容均会同步;
- 缓存的数据包括data,params,我们可以记忆上一次请求的条件,并在下次初始化;
- 在发起网络请求时,会优先返回之前缓存的数据,然后在背后发起新的网络请求,最终用新的请求结果重新触发组件渲染;
- 只有成功的请求数据才会缓存
swr 特性在特定场景,对用户非常友好
// Cache-Control:max-age=600,stale-while-revalidate=30const { data, loading } = useRequest(getArticle, {cacheKey: 'articleKey',refreshOnWindowFocus: true, // 屏幕重新聚焦或可见时,重新发起网络请求});
asyncWithRetry
/*** 网络请求失败,重新请求* @param requestFunction request* @param param {object} {retry: 3}* @returns*/export async function asyncWithRetry(requestFunction, { retry = 3 } = {}) {return new Promise((resolve, reject) => {let timer = null;function request(ms) {requestFunction().then(res => {if (timer) clearTimeout(timer)resolve(res)}).catch(e => {if (ms > 0) {timer = setTimeout(() => request(ms - 1), 500)} else {reject(e)}});}request(retry)});}
axios.interceptors.response.use() 请求重试
https://www.yuque.com/fcant/web/wf6igm
requestWithRetry
/*** 网络请求失败,重新请求* @param requestFunction {function} request 请求* @param errorFunction {function} 请求错误处理* @param retry {number} 默认 3* @returns*/export async function requestWithRetry(requestFunction, options) {const {onError,retry = 3,} = options ?? {};try {return await requestFunction();} catch (e) {if (retry <= 0) return onError ? onError(e) : null;retry -= 1;return await requestWithRetry(requestFunction, { onError, retry });}}
封装 useRequest
import request, { RequestOptionsInit } from 'umi-request';import useAsync from './useAsync';function useRequest(service: any, options?: any): any {let promiseService: () => Promise<any>;if (typeof service === 'string') {promiseService = () => request(service);} else if (typeof service === 'object') {const { url, ...rest } = service;promiseService = () => request(url, rest);} else {promiseService = (...args) =>new Promise((resolve) => {const result = service(...args);if (typeof result === 'string') {request(result).then((data) => {resolve(data);});} else if (typeof result === 'object') {const { url, ...rest } = result;request(url, rest).then((data) => {resolve(data);});}});}return useAsync(promiseService, options);}
