api-hooks 在 React 中更优雅的定义与使用 Api 请求
在 React 中轻松且规范地定义 API 请求、一键创建钩子函数,包含状态跟踪、请求中断和请求刷新,并方便进行错误处理和批量请求与进度跟踪。
安装
本轮子基于并包含了 @sttot/axios-api
,适于 React 使用,如果使用了本轮轮子就不需要额外安装 axios 和 @sttot/axios-api。
pnpm install @sttot/api-hooks
使用
可以使用 apiFactory
用指定 axios 实例创建一个 api 工厂函数:
import axios from 'axios';
import { apiFactory } from '@sttot/api-hooks';
const api = apiFactory(axios.create({}));
如果没有自定义 axios 实例的需求,也可以直接使用默认的 api 工厂函数:
import { api } from '@sttot/api-hooks';
这个 api 函数等价于 @sttot/axios-api
中使用 apiBase
创建的 api 工厂函数,具体用法请参考 @sttot/axios-api
,这里只介绍 @sttot/api-hooks
扩展的功能:
创建一个钩子函数
先从最简单的 GET 请求开始,假设已经有如下的 API 定义:
const myApi = api<number, string>(
id => ({
method: 'GET',
url: `https://api.sttot.com/test/${id}`,
}),
({ data }) => data,
);
我们希望在一个 React 组件中使用它:有一个输入 id 的输入框,请求服务器上对应的字符串并显示,可能的话,对错误进行打印、显示加载中、对错误请求进行重试并可以中止请求。可以使用该 Api 创建一个钩子函数:
import { useState } from 'react';
const useInfo = myApi.creatrMemoHook();
export default () => {
const [id, setId] = useState(0);
const [result, error, loading, cancel, retry] = useInfo(id, [id]);
return (
<div>
<input
type="number"
placeholder="查询 ID"
onChange={({ target: { value } }) => setId(Number(value) ?? 0)}
/>
{loading ? <button onClick={() => cancel()}>中止</button> : <></>}
{error ? (
<div>
出错了: {String(error)}
<button onClick={retry}>重试</button>
</div>
) : (
<></>
)}
<p>{loading ? '加载中...' : `查询结果: ${result ?? ''}`}</p>
</div>
);
};
createMemoHook
会创建一个钩子函数,该函数有两个参数,第一个参数接受的是 Api 的参数或者返回 Api 参数的函数;第二个参数和 useEffect 一样,是一个数组,只有数组中的变量改变,才会重新调用请求。
钩子返回一个数组,其中一共有五个对象:
- 第一个对象是 Api 结果,在请求进行时或出错时是 undefined;
- 第二个对象是 Api 最终抛出的结果,在请求进行时或者请求成功时是 undefined;
- 第三个对象是一个布尔值,表示现在请求是否正在进行中;
- 第四个对象是一个函数,用来中止请求,有一个可选参数用来指定中止原因;
- 第五个对象是一个函数,用来重新进行请求;
注:不需要用到的对象可以不接收,例如如下写法:
const [result] = useInfo(id, [id]);
const [result, , , , retry] = useInfo(id, [id]);
注:假如有如下代码:
const [result] = useXXX(a, [b]);
useEffect(() => {
//
}, [result, b]);
应当注意到,当 b 变化时,result 会首先变成 undefined,在请求结束后 result 又会被改变;因此 useEffect 会被调用两次而不是一次。
Reducer 钩子
有时请求 Api 并不需要一个结果,或者调用 Api 的时机并不是某个值被改变了(例如希望在点击按钮时再去查询),此时就需要使用 React 中 Reducer+Dispatch 的思想:
import { useState } from 'react';
const useInfoReducer = myApi.createReducerHook();
export default () => {
const [id, setId] = useState(0);
const {
result,
error,
pending,
idle,
fullfilled,
rejected,
dispatch,
cancel,
} = useInfoReducer();
return (
<div>
<input
type="number"
placeholder="查询 ID"
onChange={({ target: { value } }) => setId(Number(value) ?? -1)}
/>
<button onClick={() => dispatch(id)}>查询</button>
{loading ? <button onClick={() => cancel()}>中止</button> : <></>}
{error ? (
<div>
出错了: {String(error)}
<button onClick={() => dispatch(id)}>重试</button>
</div>
) : (
<></>
)}
<p>{pending ? '加载中...' : `查询结果: ${result ?? ''}`}</p>
</div>
);
};
result、error、cancel 同上,对不同的部分进行解释:
- 由于请求在一开始不会自动触发,因此会有四种状态:
- idle 尚未执行过任何请求;
- pending 正在执行请求;
- fullfilled 请求成功;
- rejected 请求失败;
- dispatch 是用发起请求的函数,第一个参数是 Api 的参数,第二个是可选参数,Api 请求回调;
除此之外,还有两个钩子,用于做批量请求,具体使用方式类似,请参考代码注释提示:
- createBatchReducerHook
- createBatchMemoHook