Atom Family

前面提到了在组件中动态创建 atom 有一种更好的方法,这就是 Atom Family。Atom Family 是jotai/utils中提供的一种扩展 atom 构建方法,可以根据给定的参数动态的创建 atom。Atom Family 使用atomFamily函数创建,这个函数针对创建的 atom 不同,有两种函数签名。

// 针对要创建的atom是原始atom
function atomFamily<Param, Value>(
  initializeAtom: (param: Param) =Atom<Value>,
  areEqual?: (a: Param, b: Param) => boolean
): (param: Param) => Atom<Value>;

// 针对要创建的atom是派生atom,可以使用更多的自定义内容
function atomFamily<Param, Value, Update>(
  initializeAtom: (param: Param) => WritableAtom<Value, Update>,
  areEqual?: (a: Param, b: Param) => boolean
): (param: Param) => WritableAtom<Value, Update>;

以上atomFamily的函数签名可以在使用 TypeScript 时显式标记泛型参数使用。但是注意如果需要创建的 atom 是一个原始 atom,那么需要仿照以下示例中的类型来辅助描述Update类型参数。

type SetStateAction<Value> = Value | ((prev: Value) => Value);

const nameFamily = atomFamily<string, string, SetStateAction<string>>(
  (name: string) => atom(name)
);

Atom Family 在底层使用一个 Map 来维护所提供的Param与创建的 atom 之间的对应关系,如果提供的param经过areEqual参数提供的一致性判定,那么atomFamily将会将缓存的 atom 提供出来。在默认情况下调用atomFamily时不需要提供areEqual参数,Jotai 默认会使用Object.is来完成param的一致性判定。

以下通过一个较为复杂的示例来说明 Atom Family 的使用。

const productAmountRecordAtom = atom<Record<string, number>>({});
const productAmountAtom = atomFamily<string, number, SetStateAction<number>>(
  (name) =>
    atom(
      (get) => get(productAmountRecordAtom)[name] ?? null,
      (get, set, amount) => {
        const prev = get(productAmountRecordAtom);
        set(productAmountRecordAtom, {
          ...prev,
          [name]: amount,
        });
      }
    )
);

const ProductDetail: FC<{ name: string }> = ({ name }) => {
  const [amount, setAmount] = useAtom(productAmountAtom(name));

  // 以下省略React组件的实现以及其他操作代码
};

Caution

这里在对Atom Family创建的atom进行更新的时候,需要注意在atomFamily构建atom的写入代码时,所要更新的是当前atom所依赖的atom,而不是当前atomFamily创建的atom,所以要进行的操作不只是当前atom而是其所依赖的完整atom,不要因为仅更新当前atom的内容致使依赖atom的内容发生丢失。