为什么选择ahooks

165 阅读7分钟

浅析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 管理的底层能力,故有以下建议:

  1. 期望 service 返回最终格式的数据。
  2. 并行模式删除,期望将每个请求动作和 UI 封装为一个组件,而不是把所有请求都放到父级。
  3. 不再支持字符或对象,期望基于 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:

  1. useAntdTable 会自动管理 Table 分页数据
  2. 分页切换时自动发起异步请求
  3. 基于useRequest,除部分区别外,可使用useRequest提供的刷新、loading、cache等能力
  4. 可传入 form 实例实现Form 与 Table 联动,通过额外返回 的 search 字段,处理表单相关事件
  5. 组件卸载时取消还在进行中的异步请求,防止可能报警的情况发生

总结

综上所述,ahooks具有【大厂背书、风格清爽、更贴合业务】的特点。我们选择使用ahooks,一方面可以更加快速灵活的完成开发任务,另一方面,可以探索其沉淀出的各种新特性,从api的使用到更多特性的学习,更能够额外提升开发能力,并且掌握更多优化项目的思路。除此之外,我们也可以从UI、交互、服务等各个方面,向更成熟的阿里系靠拢,既可以提升团队交流的效率,也可以做更多业务开发外的事情。