https://ahooks.js.org/zh-CN/guide
https://github.com/umijs/umi-request

useRequest规范

请求层治理:

  1. 统一请求库,新建应用,不需要重复实现一套请求层逻辑
  2. 规范请求接口设计规范
  3. 统一接口文档,应用接口设计一致
    1. 减少开发者在接口设计、文档维护、请求层逻辑开发上花费的沟通和人力成本

image.png

useRequest特点

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

image.png
useRequest 参考
https://www.yuque.com/mayiprototeam/gfyt69/ggqy8q

  1. const {
  2. loading: boolean,
  3. data?: TData,
  4. error?: Error,
  5. params: TParams || [],
  6. run: (...params: TParams) => void,
  7. runAsync: (...params: TParams) => Promise<TData>,
  8. refresh: () => void,
  9. refreshAsync: () => Promise<TData>,
  10. // 通过 mutate,直接修改 data
  11. mutate: (data?: TData | ((oldData?: TData) => (TData | undefined))) => void,
  12. cancel: () => void,
  13. } = useRequest<TData, TParams>(
  14. service: (...args: TParams) => Promise<TData>,
  15. {
  16. manual?: boolean,
  17. defaultParams?: TParams,
  18. onBefore?: (params: TParams) => void,
  19. onSuccess?: (data: TData, params: TParams) => void,
  20. onError?: (e: Error, params: TParams) => void,
  21. onFinally?: (params: TParams, data?: TData, e?: Error) => void,
  22. }
  23. );

run 轮询请求

manual = true ,即可阻止 useRequest 初始化执行。只有触发 run 时才会开始执行
polling 轮询文档 https://ahooks.js.org/zh-CN/hooks/use-request/polling

  1. import { useRequest } from 'ahooks';
  2. import { message } from 'antd';
  3. function App() {
  4. const { data, loading, run, cancel } = useRequest(fetchUser, {
  5. manual: true, // 手动请求
  6. retryCount: 3, // 请求异常后,3 次重试;第一次重试等待 2s,第二次重试等待 4s
  7. pollingInterval: 60000, // ms 毫秒,轮询模式,定时 1分钟触发一次
  8. pollingErrorRetryCount: 5, // 轮询错误重试次数
  9. pollingWhenHidden: false, // 页面隐藏时,暂时停止轮询;显示时继续上次轮询
  10. loadingDelay: 400, // ms 延迟 loading 变成 true 的时间,防止闪烁
  11. onError: (error) => {
  12. // 错误信息提示
  13. message.error(error.message);
  14. },
  15. });
  16. useEffect(() => {
  17. run(id); // run传参
  18. return () => {
  19. cancel() // cancel 停止轮询
  20. }
  21. }, [id])
  22. }
  23. export function fetchUser() {
  24. return fetch('/api/user')
  25. }
  • 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

failed to load

return ( ) }

  1. <a name="uoUNE"></a>
  2. ### visibilitychange 标签页隐藏或显示事件
  3. 浏览器标签页被隐藏或显示的时候,会触发 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)
  4. - document.visibilityState取值:hidden,visible;
  5. - document.hidden取值:true/false
  6. <a name="tysxy"></a>
  7. ### visibilitychange场景
  8. - 监听返回、分享、跳转、切入后台;
  9. - 用于控制播放音乐视频、轮播等循环动画效果等;
  10. - 可用于统计某个页面用户停留时长,即显示时开始计算,隐藏时结束计算
  11. <a name="Em6tK"></a>
  12. ### refreshDeps 依赖参数请求
  13. 依赖改变,刷新请求
  14. ```jsx
  15. const { data, run, loading } = useRequest(() => getUserSchool(userId), {
  16. // 当数组内容变化后,发起请求,类似 useEffect的参数
  17. refreshDeps: [userId],
  18. debounceWait: 1000, // ms 频繁触发 run/runAsync,会以防抖策略进行请求,一秒
  19. // throttleWait: 1000, // 节流
  20. })
  21. // refresh 刷新
  22. const { data, refresh } = useRequest(() => getUserSchool(userId));
  23. useEffect(() => {
  24. refresh();
  25. }, [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 }, }));

  1. <a name="LfdOn"></a>
  2. ### 并行请求 ❌
  3. 也就是同一个接口,我们需要维护多个请求状态<br />ahooks 3.x 取消了并行请求<br />[https://github.com/alibaba/hooks/issues/1378](https://github.com/alibaba/hooks/issues/1378)<br />原因:
  4. - useRequest 实现并行能力复杂度太高
  5. - 建议将 请求和UI 封装成一个组件实现并行请求的能力。而不是把所有请求都放到父级;
  6. - 虚拟滚动场景,确实会有问题。这种场景建议自行管理请求状态
  7. <a name="dtotT"></a>
  8. ### useRequest 生命周期
  9. ```javascript
  10. const { data, loading, run, cancel } = useRequest(fetchUser, {
  11. manual: true, // 手动请求
  12. // 请求之前
  13. onBefore: () => { },
  14. // 请求成功触发
  15. onSuccess: (res) => { console.log(res); },
  16. // 请求完成
  17. onFinally: () => { },
  18. // 请求失败
  19. onError: (error) => {
  20. // 错误信息提示
  21. message.error(error.message);
  22. },
  23. });
  24. const { data, loading, run } = useRequest(data => ({
  25. // data是 run传进来的
  26. url: "http://127.0.0.1:8080/api/user",
  27. method: "POST",
  28. body: JSON.stringify(data),
  29. headers: {
  30. 'Content-Type': 'application/json;charset=utf-8'
  31. }
  32. }), {
  33. manual: true,
  34. onSuccess: (result) => {
  35. if (result.status === 'ok') {
  36. message.success('流程已经提交');
  37. }
  38. }
  39. });

usePagination分页

基于 useRequest 实现,封装了常见的分页逻辑;page、pageSize、total 管理
https://ahooks.js.org/zh-CN/hooks/use-pagination

筛选条件变化,重置分页,重新发起网络请求
usePagination 分页请求,不支持手动触发、不支持轮询等

  1. import React from 'react';
  2. import { usePagination } from 'ahooks';
  3. import { List } from 'antd';
  4. async function getUserList({
  5. current,
  6. pageSize,
  7. }): Promise<{ total: number; list: UserListItem[] }> {
  8. return fetch('/api/user', {current, pageSize})
  9. }
  10. export default () => {
  11. const { data, loading, pagination } = usePagination(getUserList);
  12. return (
  13. <List
  14. loading={loading}
  15. dataSource={data}
  16. renderItem={(item) => (
  17. <List.Item>
  18. <List.Item.Meta
  19. avatar={<Avatar src="https://joesch.moe/api/v1/random" />}
  20. title={<a href="https://ant.design">{item.title}</a>}
  21. description="Ant Design for background applications"
  22. />
  23. </List.Item>
  24. )}
  25. pagination={{
  26. position: 'bottom',
  27. align: 'right',
  28. current: pagination.current,
  29. pageSize: pagination.pageSize,
  30. onChange: pagination.onChange,
  31. onShowSizeChange: pagination.onChange,
  32. showQuickJumper: true,
  33. showSizeChanger: true,
  34. total: data.total
  35. }}
  36. />
  37. );
  38. };

网络请求失败自动重试

SWR 缓存 & 预加载

swr,stale-while-revalidate 在发起网络请求时,会优先返回之前缓存的数据
stale-whie-revalidate是 HTTP RFC 5861 中描述的一种 Cache-Control 扩展,属于对缓存到期的控制

  • options.cacheKey,将当前请求成功的数据缓存起来
    1. 下次组件初始话时,如果有缓存数据,会优先返回缓存数据,然后在背后发送新请求,先使用旧的,如果有新的数据,就会拿新的替换
  • staleTime 数据保持新鲜时间
    • 在这个时间内,认为数据时新鲜的,不会重新发送请求
    • 设置为 -1,表示数据永远新鲜
  • cacheTime 数据缓存时间
    • 超过这个时间,会清空这条数据缓存,缓存的回收时间,默认5分钟后回收
    • 设置为-1,表示缓存数据永远不会过期
  1. import { useRequest,clearCache } from 'ahooks'
  2. const { data, loading, run, refresh, cancel } = useRequest(fetchUser, {
  3. cacheKey: 'cacheUser',
  4. staleTime: 60000 * 10, // ms
  5. retryCount: 5, // 错误重试
  6. });

对于一些数据不是经常变化的接口,使用 SWR 后,可以极大提高用户使用体验
配置 cacheKey ,即可进入 SWR 模式
同一个 cacheyKey 的数据是全局共享的。通过这个特性,可以实现“预加载”功能;

  • 当我们改变其中某个cacheKey的内容时,其他相同cacheKey的内容均会同步;
  • 缓存的数据包括data,params,我们可以记忆上一次请求的条件,并在下次初始化;
  • 在发起网络请求时,会优先返回之前缓存的数据,然后在背后发起新的网络请求,最终用新的请求结果重新触发组件渲染;
  • 只有成功的请求数据才会缓存

swr 特性在特定场景,对用户非常友好

  1. // Cache-Control:max-age=600,stale-while-revalidate=30
  2. const { data, loading } = useRequest(getArticle, {
  3. cacheKey: 'articleKey',
  4. refreshOnWindowFocus: true, // 屏幕重新聚焦或可见时,重新发起网络请求
  5. });

asyncWithRetry

  1. /**
  2. * 网络请求失败,重新请求
  3. * @param requestFunction request
  4. * @param param {object} {retry: 3}
  5. * @returns
  6. */
  7. export async function asyncWithRetry(requestFunction, { retry = 3 } = {}) {
  8. return new Promise((resolve, reject) => {
  9. let timer = null;
  10. function request(ms) {
  11. requestFunction()
  12. .then(res => {
  13. if (timer) clearTimeout(timer)
  14. resolve(res)
  15. })
  16. .catch(e => {
  17. if (ms > 0) {
  18. timer = setTimeout(() => request(ms - 1), 500)
  19. } else {
  20. reject(e)
  21. }
  22. });
  23. }
  24. request(retry)
  25. });
  26. }

axios.interceptors.response.use() 请求重试
https://www.yuque.com/fcant/web/wf6igm

requestWithRetry

  1. /**
  2. * 网络请求失败,重新请求
  3. * @param requestFunction {function} request 请求
  4. * @param errorFunction {function} 请求错误处理
  5. * @param retry {number} 默认 3
  6. * @returns
  7. */
  8. export async function requestWithRetry(requestFunction, options) {
  9. const {
  10. onError,
  11. retry = 3,
  12. } = options ?? {};
  13. try {
  14. return await requestFunction();
  15. } catch (e) {
  16. if (retry <= 0) return onError ? onError(e) : null;
  17. retry -= 1;
  18. return await requestWithRetry(requestFunction, { onError, retry });
  19. }
  20. }

封装 useRequest

  1. import request, { RequestOptionsInit } from 'umi-request';
  2. import useAsync from './useAsync';
  3. function useRequest(service: any, options?: any): any {
  4. let promiseService: () => Promise<any>;
  5. if (typeof service === 'string') {
  6. promiseService = () => request(service);
  7. } else if (typeof service === 'object') {
  8. const { url, ...rest } = service;
  9. promiseService = () => request(url, rest);
  10. } else {
  11. promiseService = (...args) =>
  12. new Promise((resolve) => {
  13. const result = service(...args);
  14. if (typeof result === 'string') {
  15. request(result).then((data) => {
  16. resolve(data);
  17. });
  18. } else if (typeof result === 'object') {
  19. const { url, ...rest } = result;
  20. request(url, rest).then((data) => {
  21. resolve(data);
  22. });
  23. }
  24. });
  25. }
  26. return useAsync(promiseService, options);
  27. }