11

我第一次玩反冲,不知道如何从 atomFamily 中读取所有元素。假设我有一个应用程序,用户可以在其中添加餐点:

export const meals = atomFamily({
  key: "meals",
  default: {}
});

我可以如下初始化一顿饭:

const [meal, setMeal] = useRecoilState(meals("bananas"));
const bananas = setMeal({name: "bananas", price: 5});

我如何阅读已添加到此 atomFamily 的所有项目?

4

4 回答 4

14

您必须跟踪所有 idatomFamily才能获取家庭的所有成员。请记住,这并不是一个真正的列表,更像是一张地图。

像这样的东西应该让你去。

// atomFamily
const meals = atomFamily({
  key: "meals",
  default: {}
});

const mealIds = atom({
  key: "mealsIds",
  default: []
});

在族内创建新对象时,您还必须更新mealIds原子。

我通常使用useRecoilCallback钩子将它们同步在一起

  const createMeal = useRecoilCallback(({ set }) => (mealId, price) => {
    set(mealIds, currVal => [...currVal, mealId]);
    set(meals(mealId), {name: mealId, price});
  }, []);

这样,您可以通过调用以下方式创建餐点:

createMeal("bananas", 5);

并通过以下方式获取所有 ID:

const ids = useRecoilValue(mealIds);
于 2020-10-20T20:03:02.810 回答
12

而不是使用useRecoilCallback你可以抽象它与selectorFamily.

// atomFamily
const mealsAtom = atomFamily({
  key: "meals",
  default: {}
});

const mealIds = atom({
  key: "mealsIds",
  default: []
});

// abstraction
const meals = selectorFamily({
  key: "meals-access",
  get:  (id) => ({ get }) => {
      const atom = get(mealsAtom(id));
      return atom;
  },
  set: (id) => ({set}, meal) => {
      set(mealsAtom(id), meal);
      set(mealIds (id), prev => [...prev, meal.id)]);
  }
});

此外,如果您想支持重置,您可以使用以下代码:

// atomFamily
const mealsAtom = atomFamily({
  key: "meals",
  default: {}
});

const mealIds = atom({
  key: "mealsIds",
  default: []
});

// abstraction
const meals = selectorFamily({
  key: "meals-access",
  get:  (id) => ({ get }) => {
      const atom = get(mealsAtom(id));
      return atom;
  },
  set: (id) => ({set, reset}, meal) => {
      if(meal instanceof DefaultValue) {
        // DefaultValue means reset context
        reset(mealsAtom(id));
        reset(mealIds (id));
        return;
      }
      set(mealsAtom(id), meal);
      set(mealIds (id), prev => [...prev, meal.id)]);
  }
});

如果您使用的是 Typescript,则可以使用以下guard使其更加优雅。

import { DefaultValue } from 'recoil';

export const guardRecoilDefaultValue = (
  candidate: unknown
): candidate is DefaultValue => {
  if (candidate instanceof DefaultValue) return true;
  return false;
};

将此防护与 Typescript 一起使用将如下所示:

// atomFamily
const mealsAtom = atomFamily<IMeal, number>({
  key: "meals",
  default: {}
});

const mealIds = atom<number[]>({
  key: "mealsIds",
  default: []
});

// abstraction
const meals = selectorFamily<IMeal, number>({
  key: "meals-access",
  get:  (id) => ({ get }) => {
      const atom = get(mealsAtom(id));
      return atom;
  },
  set: (id) => ({set, reset}, meal) => {
      if (guardRecoilDefaultValue(meal)) {
        // DefaultValue means reset context
        reset(mealsAtom(id));
        reset(mealIds (id));
        return;
      }
      // from this line you got IMeal (not IMeal | DefaultValue)
      set(mealsAtom(id), meal);
      set(mealIds (id), prev => [...prev, meal.id)]);
  }
});
于 2020-12-09T12:30:59.357 回答
2

您可以使用 anatom来跟踪atomFamily. 然后selectorFamily,当从atomFamily. 然后,可以使用具有 id 列表的原子从selectorFamily.

// File for managing state


//Atom Family
export const mealsAtom = atomFamily({
  key: "meals",
  default: {},
});
//Atom ids list
export const mealsIds = atom({
  key: "mealsIds",
  default: [],
});

这是看起来的selectorFamily样子:

// File for managing state

export const mealsSelector = selectorFamily({
  key: "mealsSelector",
  get: (mealId) => ({get}) => {
    return get(meals(mealId));
  },
  set: (mealId) => ({set, reset}, newMeal) => {
    // if 'newMeal' is an instance of Default value, 
    // the 'set' method will delete the atom from the atomFamily.
    if (newMeal instanceof DefaultValue) {
      // reset method deletes the atom from atomFamily. Then update ids list.
      reset(mealsAtom(mealId));
      set(mealsIds, (prevValue) => prevValue.filter((id) => id !== mealId));
    } else {
      // creates the atom and update the ids list
      set(mealsAtom(mealId), newMeal);
      set(mealsIds, (prev) => [...prev, mealId]);
    }
  },
});

现在,你如何使用这一切?

  • 创建一顿饭:

在这种情况下,我使用当前时间戳作为原子 IDMath.random()

// Component to consume state

import {mealsSelector} from "your/path";
import {useSetRecoilState} from "recoil";
const setMeal = useSetRecoilState(mealsSelector(Math.random()));

setMeal({
    name: "banana",
    price: 5,
});

  • 删除一顿饭:
// Component to consume state

import {mealsSelector} from "your/path";
import {DefaultValue, useSetRecoilState} from "recoil";

const setMeal = useSetRecoilState(mealsSelector(mealId));
setMeal(new DefaultValue());
  • 从 atomFamily 获取所有原子:

循环 id 列表并渲染接收 id 作为道具的 Meals 组件,并使用它来获取每个原子的状态。

// Component to consume state, parent of Meals component

import {mealsIds} from "your/path";
import {useRecoilValue} from "recoil";

const mealIdsList = useRecoilValue(mealsIds);

    //Inside the return function:
    return(
      {mealIdsList.slice()
          .map((mealId) => (
            <MealComponent
              key={mealId}
              id={mealId}
            />
          ))}
    );
// Meal component to consume state

import {mealsSelector} from "your/path";
import {useRecoilValue} from "recoil";

const meal = useRecoilValue(mealsSelector(props.id));

然后,您有一个 Meals 组件列表,每个组件都有自己的状态atomFamily

于 2021-10-23T18:25:59.360 回答
1

这是我在当前项目中的工作方式:

(对于上下文,这是一个从字段选项对象数组创建的动态表单。表单值是通过 graphql 突变提交的,因此我们只需要进行最少的更改。因此,表单是在用户编辑字段时构建的)

import { atom, atomFamily, DefaultValue, selectorFamily } from 'recoil';

type PossibleFormValue = string | null | undefined;

export const fieldStateAtom = atomFamily<PossibleFormValue, string>({
  key: 'fieldState',
  default: undefined,
});

export const fieldIdsAtom = atom<string[]>({
  key: 'fieldIds',
  default: [],
});

export const fieldStateSelector = selectorFamily<PossibleFormValue, string>({
  key: 'fieldStateSelector',
  get: (fieldId) => ({ get }) => get(fieldStateAtom(fieldId)),
  set: (fieldId) => ({ set, get }, fieldValue) => {
    set(fieldStateAtom(fieldId), fieldValue);
    const fieldIds = get(fieldIdsAtom);
    if (!fieldIds.includes(fieldId)) {
      set(fieldIdsAtom, (prev) => [...prev, fieldId]);
    }
  },
});

export const formStateSelector = selectorFamily<
  Record<string, PossibleFormValue>,
  string[]
>({
  key: 'formStateSelector',
  get: (fieldIds) => ({ get }) => {
    return fieldIds.reduce<Record<string, PossibleFormValue>>(
      (result, fieldId) => {
        const fieldValue = get(fieldStateAtom(fieldId));
        return {
          ...result,
          [fieldId]: fieldValue,
        };
      },
      {},
    );
  },
  set: (fieldIds) => ({ get, set, reset }, newValue) => {
    if (newValue instanceof DefaultValue) {
      reset(fieldIdsAtom);
      const fieldIds = get(fieldIdsAtom);
      fieldIds.forEach((fieldId) => reset(fieldStateAtom(fieldId)));
    } else {
      set(fieldIdsAtom, Object.keys(newValue));
      fieldIds.forEach((fieldId) => {
        set(fieldStateAtom(fieldId), newValue[fieldId]);
      });
    }
  },
});

原子选择器在应用程序的 3 个地方使用:

在字段组件中:

...
const localValue = useRecoilValue(fieldStateAtom(fieldId));
const setFieldValue = useSetRecoilState(fieldStateSelector(fieldId));
...

在保存处理组件中(尽管在带有显式提交按钮的表单中这可能更简单):

...
const fieldIds = useRecoilValue(fieldIdsAtom);
const formState = useRecoilValue(formStateSelector(fieldIds));
...

在另一个处理表单操作的组件中,包括表单重置:

...
const resetFormState = useResetRecoilState(formStateSelector([]));
...
const handleDiscard = React.useCallback(() => {
  ...
  resetFormState();
  ...
}, [..., resetFormState]);
于 2021-04-21T17:39:50.900 回答