React Hooks 自发布以来,因其简单符合直觉的 API 与灵活的组合能力,很快就在 LeanCloud 控制台的重构项目中得到了广泛使用。对于「从服务端加载组件所需数据」这一需求,因为最开始的需求比较简单,我们没有引入第三方库而是自己封装了一些 API 来实现。随着重构的进行,这些 API 逐渐演化并形成了一套相对完整的方案。在对比了社区中其他的一些热门「加载数据 Hook 库」之后,我们发现社区中很少有对类似的设计方案的讨论。这篇文章将介绍这个方案是如何演进,以及它是如何以一种更加符合「Hook」设计风格的方式来满足我们遇到的各种需求的。

方案的源码开放在了 GitHub 上:https://github.com/leancloud/use-resource

内容分为三个部分:

  1. 核心方法(createResourceHook
  2. 扩展功能
  3. 特点与优势

核心方法(createResourceHook)

我们的探索开始于最简单的需求:使用 hook 加载一个 REST API。在这个场景中,我们关注的状态有三个:(成功的)结果、异常以及是否正在加载。因此在设计中,我们的代码看起来应该是这个样子的:

const Clock = () => {
  const [data, { error, loading }] = useFetch(['<https://worldtimeapi.org/api/timezone/etc/utc>']);

  return (
    <div>
      {loading && 'Loading...'}
      {error && error.message}
      {data && data.datetime}
    </div>
  );
};

useFetch 的第一个参数是 fetch 的参数列表,返回三个状态 dataerrorloading。因为 data 几乎是一定会用到的,同时为了方便使用的地方对其重命名,我们将其单独作为 tuple 的第一个元素返回。

这篇文章接下来讨论的所有功能与扩展都将基于这个useFetch hook。useFetch 是由一个叫做 createResourceHook 的方法创建的。createResourceHook 是这个方案中最核心的 API,它会将一个请求数据的方法(比如 fetch)转换为一个 hook,其定义如下:

function createResourceHook<Args extends unknown[], T>(
  requestFn: (...args: Args) => Promise<T>
): ResourceHook<Args, T>;

type ResourceHook<Args, T> = (
  requestArgs: Args,
  options?: { deps?: DependencyList; condition?: boolean; }
) => Resource<T | undefined>;

比如上面例子中的 useFetch 就是使用 createResourceHook 「包装」的 fetch

import { createResourceHook } from '@leancloud/use-resource';

const fetchJSON = async (...args) => (await fetch(...args)).json();

export const useFetch = createResourceHook(fetchJSON);

除了最基础的用法,通过 createResourceHook 创建的 hook(下文统称为 useResource)还支持以下的特性:

指定依赖