浅析React Hooks
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
众所周知,React Hooks的出现,源于React通过引入Fiber结构,对核心内容进行了重新实现。 将渲染工作分片并将其分散到多个帧中,加入了在新更新进入时暂停、中止、重复工作的能力,并且为不同类型的更新分配优先级,以此解决了渲染复杂组件时严重影响用户和浏览器交互的问题。
从 React Hooks 正式发布到现在,越来越多的项目正在使用 Function Component 替代 Class Component,Hooks 这一新特性也逐渐被广泛的使用。在实践的过程中,我们发现在很多常见的场景下,大部分逻辑是重复且可被复用的,比如对数据请求的逻辑处理,对防抖节流的逻辑处理等等,同样的代码经常会在同一个或不同的项目中被重复的编写。
官方在解释自定义hooks时有提到:“通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。”根据该特性,我们可以在组件之间共享可复用的状态逻辑,方便了开发者将业务逻辑和 UI 视图进行解耦,从而状态与 UI 的界限会越来越清晰,顺着这个思路,我们便可以将与业务无关的逻辑进行抽象,封装一套通用场景的纯逻辑的 Hooks 工具方法。
什么是ahooks
ahooks:基于 React Hooks 的工具库,致力提供常用且高质量的 Hooks。
背景
随着 React Hooks 的发展,Hooks 正逐渐成为 React 组件的主流写法。得益于 Hooks 的逻辑封装能力,很多团队都开始建设自己的 Hooks 库,但在建设过程中,会发现各个 Hooks 库提供的 Hooks 大同小异,尤其是基础类 Hooks 几乎都是一样的。
起初常被提到的社区上Hooks工具包含了:粒度较小、不包含组件逻辑复用、涵盖100+hook的react-use ,蚂蚁开源基于umi团队项目的 @umijs/hooks,面向淘系中后台业务场景的ice/hooks等等。基于避免重复建设的目的,以及 umi hooks 的积累,最终淘系 ice 团队与蚂蚁 umi 团队、阿里体育团队达成共建一套阿里集团通用 Hooks 方案的目标,其预期提供的Hooks 一方面是基于 react-use 的基础部分,另一方面更多的是贴合业务的,由业务中进行提炼出来的 Hooks 进行组合的方案,这便是 ahooks 工具库的由来,目前已发布v3.0。
特性
- 易学易用
- 支持 SSR
- 对输入输出函数做了特殊处理,且避免闭包问题
- 包含大量提炼自业务的高级 Hooks
- 包含丰富的基础 Hooks
- 使用 TypeScript 构建,提供完整的类型定义文件
API规范
ahooks 基于 UI、SideEffect、LifeCycle、State、DOM 等分类提供了常用的 Hooks,如下图所示。并对每一类接口的 API 进行了规范化,如规范入参结构、返回值结构等,保证 API 层面的简洁和一致性。
蚂蚁中台标准请求 Hook - useRequest
简介
useRequest 是一个强大的异步数据管理的 Hooks,React 项目中的网络请求场景使用 useRequest 就足够,目前已经成为蚂蚁中台最佳实践内置网络请求方案。
为什么要做 useRequest?
在react开发中,我们常常会在组建内请求数据,并且需要考虑loading、防抖节流、错误重试等等,并且会在组件内维护很多管理api状态的state。而useRequest 通过插件式组织代码,核心代码极其简单,并且可以很方便的扩展出更高级的功能。
基本用法
useRequest 接收了一个异步函数。在组件初始化时,会自动触发该异步函数,并自动管理 data 、 loading 、 error 等状态,我们只需要根据需要来写相应的 UI 即可。
```
const { data, error, loading } = useRequest(service);
```
const {
loading: boolean,
data?: TData,
error?: Error,
params: TParams || [],
run: (...params: TParams) => void,
runAsync: (...params: TParams) => Promise<TData>,
refresh: () => void,
refreshAsync: () => Promise<TData>,
mutate: (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,
}
);
轮询
手机扫描二维码登录的需求,常使用轮询的方式,检查用户是否成功扫描。诸如此类的轮询场景,useRequest 只要配置 poilingInterval 即可完成自动定时发起网络请求。
同时通过设置 pollingWhenHidden ,我们可以智能的实现在屏幕隐藏时,暂停轮询。等屏幕恢复可见时,继续请求,以节省资源。
屏幕聚焦重新请求
通过设置 options.refreshOnWindowFocus,在浏览器窗口 refocus 和 revisible 时,会重新发起请求。
```
const { data } = useRequest(getUsername, { refreshOnWindowFocus: true });
```
缓存 & SWR
swr 是 stale-while-revalidate 的简称,最主要的能力是:我们在发起网络请求时,会优先返回之前缓存的数据,然后在背后发起新的网络请求,最终用新的请求结果重新触发组件渲染。swr 特性在特定场景,对用户非常友好。
通过向useRequest配置options.cacheKey,开启SWR的能力。
通过options.staleTime 设置数据保持新鲜时间,在新鲜时间内,不会重新发起请求。
通过 options.cacheTime 设置数据缓存时间,超过该时间,会清空该条缓存数据。
FAQ
若只想使用 useRequest,可以单独通过安装 @ahooksjs/use-request 来使用,不必安装整个 ahooks。若只想用其中一两个 Hooks,可以进行 按需加载,不必使项目编译后所有的 Hooks 都编译进去。
官方定义v3.0的 useRequest 只做 Promise 管理的底层能力,故有以下建议:
- 期望 service 返回最终格式的数据。
- 并行模式删除,期望将每个请求动作和 UI 封装为一个组件,而不是把所有请求都放到父级。
- 不再支持字符或对象,期望基于 useReqeust 封装高级 Hooks 来支持。
/** 获取信息Api */
export const getInfo = async (key?: number): Promise<Types.Info> => {
const res = await request({
url,
method: 'get',
});
/** Api层处理返回期望格式 */
return res.data.list;
};
const initData = { title: '主题' };
/** 通过解构赋值初始化数据 */
const { data = initData, refresh } = useRequest(getInfo);
/** 将请求与组建封装,通过ref为任意父组件提供操作请求的能力 */
React.useImperativeHandle(ref, () => ({
refresh,
}));
贴合业务的Hook - useAntdTable
一行代码封装所有逻辑,列表页开发变得更加简单。
const MyTable = forwardRef((props: ConsumptionDetailsProps) => {
const { data } = props;
const antdTableResult = useAntdTable(
(paginationParams, formParams) =>
getConsumeDetail({
...paginationParams,
...formParams,
pageNum: paginationParams.current,
pageSize: paginationParams.pageSize,
}),
{
manual: true,
},
);
const { tableProps, search, run } = antdTableResult;
useEffect(() => {
run(
{ ...tableProps.pagination },
{ type: data?.type },
);
}, [data]);
const columns = [...];
return (
<Table columns={columns} {...tableProps} />
);
});
export default MyTable;
如上述示例,通过该hook,可以快速开发Table:
- useAntdTable 会自动管理 Table 分页数据
- 分页切换时自动发起异步请求
- 基于useRequest,除部分区别外,可使用useRequest提供的刷新、loading、cache等能力
- 可传入 form 实例实现Form 与 Table 联动,通过额外返回 的 search 字段,处理表单相关事件
- 组件卸载时取消还在进行中的异步请求,防止可能报警的情况发生
总结
综上所述,ahooks具有【大厂背书、风格清爽、更贴合业务】的特点。我们选择使用ahooks,一方面可以更加快速灵活的完成开发任务,另一方面,可以探索其沉淀出的各种新特性,从api的使用到更多特性的学习,更能够额外提升开发能力,并且掌握更多优化项目的思路。除此之外,我们也可以从UI、交互、服务等各个方面,向更成熟的阿里系靠拢,既可以提升团队交流的效率,也可以做更多业务开发外的事情。