经过以上几篇文章,已经可以很好地处理 batch 请求的场景了。
但是每次还是得手动处理 loading 和 error,不够方便。
况且现在已经是 hooks 的时代了,本文便引入了 useRequest 的概念。
useRequest 的使用
我们对 useFetch 的使用习惯肯定是要符合我们对 hooks 的直觉,如此这般:
1 2 3 4
| const { data, loading, error } = useRequest( { url: 'url', data: {}, ...options }, [...deps] );
|
然后直接使用 data 渲染数据和控制空态,用 loading 展示加载态,用 error 展示错误态和错误信息。
由此见得,useRequest
的使用场景,主要是查询类的被动请求,像是点击操作这类的主动请求不太适合。
useRequest 的实现
useRequest
的实现原理也很简单,相当于是自定义 hooks,如下:
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 31 32 33 34 35 36 37 38 39 40 41 42
|
export const useRequest = ( { url, data }: IUseRequestProps, deps: any[] = [] ) => { const stringifyData = JSON.stringify(data); const [state, setState] = useState<IObject | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState('');
useEffect(() => { setLoading(true); fetchBatch( url, { data: JSON.parse(stringifyData) }, (res: IObject) => { withBatch(() => { setState(res); setLoading(false); }); }, (err: Error) => { withBatch(() => { setError(err.message); setLoading(false); }); } ); }, [url, stringifyData, ...deps]);
return { data: state, loading, error }; };
|
额外功能
上文只是实现了最基础的功能,在实际使用中经常会不满足需求,于是又产生了下文的几个功能点。
主动修改数据
因为数据是存在黑盒里的,用户无法进行受控的修改,所以其实可以把 setState
给暴露出去。
主动控制触发
增加 manual
配置和 run
方法。
请求频次
因为是 hooks 的写法,只有依赖更新的时候才会重新发请求,这样的话一些轮询的接口需要借助一个布尔值,定时器每次对布尔值取反来实现,这样很不优雅。
可以增加一个 frequncy
参数,内置一个定时器发送的功能。
延迟展示的额外数据
某些场景下,我们可能需要一些数据在请求返回结果之后再一起更新,而这些数据不是从返回结果里取的,而是我们手动设置的。
这时候如果我们通过 loading
来进行判断会过于复杂,所以也可以增加一个 extraData
的功能。
SWR
即请求缓存的功能,在请求回来之前,不是展示空数据,而是展示上一次同样正确请求的数据来预先展示。
这个功能需要用个有 getCache
和 setCache
的库来实现即可,本文用的是 localStorage
的实现方式。
将以上几点修改后,代码如下:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
|
interface IUseRequestProps { url: string; data: IObject; extraData?: IObject; frequence?: number; manual?: boolean; }
export const useRequest = ( { url, data, extraData = {}, frequence = -1, manual = false }: IUseRequestProps, deps: any[] = [] ) => { const stringifyData = JSON.stringify(data); const stringifyExtraData = JSON.stringify(extraData); const localData = localStorage.getItem(`${url}/${stringifyData}`); const [state, setState] = useState<any>(localData ? JSON.parse(localData) : null); const [loading, setLoading] = useState(!manual); const [error, setError] = useState('');
const onFetch = useCallback(() => { fetchBatch( url, { data: JSON.parse(stringifyData) }, (res: any) => { withBatch(() => { const result = { ...res, ...JSON.parse(stringifyExtraData) }; setLoading(false); setState(result); localStorage.setItem(`${url}/${stringifyData}`, JSON.stringify(result)); }); }, (err: Error) => { withBatch(() => { setLoading(false); setError(err.message); }); } ); }, [url, stringifyData, stringifyExtraData]);
useEffect(() => { setLoading(true); onFetch();
if (frequence && frequence !== -1 && Number.isInteger(frequence)) { const timer = setInterval(onFetch, Math.max(frequence, 1000));
return () => { clearInterval(timer); }; }
return () => {}; }, [onFetch, frequence, ...deps]);
return { data: state, setData: setState, loading, error, run: onFetch }; };
|