🤖

recoilを理解する

2022/07/11に公開

今更ながらrecoilを勉強したので、そのまとめを書きます。
所感としては、reduxよりも書きやすく、学習コストも低いなと感じました。
基本的には公式ドキュメントをベースに説明しますので、そちらも併せて確認してください。

セットアップ

当たり前の、yarn add recoil。もしくはnpm install recoil
その上で、RecoilRootで囲っときましょう。

import { RecoilRoot } from "recoil";

export const App: React.FC = () => {
  return (
    <RecoilRoot>
      <Component />
    </RecoilRoot>
  );
};

これでRecoilRoot配下でrecoilが使えるようになります。

基本概念

Atom

recoilではAtomという単位が最小単位となります。
Atomは1つのstateを意味します。

import { atom } from "recoil";

export const UserName = atom({
  key: "UserName",
  default: "",
});

上記サンプルでは、Userの名前を想定したatomです。
keyにプロジェクト固有の文字列を設定し、defaultにデフォルト値を設定します。

selector

selectorには、stateを更新したり加工するロジックを書くことができます。

const selector = selector<T>({
  key: "uniqueKey",
  get: ({ get }) => {
    // 引数にatomやselectorを入れて現在のstateを取得。その上でatomを利用したロジックを書くことができる。
    get(atom);
  },
  set: ({ set }, newValue) => {
    // 第一引数にatomを指定し、第二引数に呼び出し側で入力した値が入る。
    set(atom, newValue);
  }
});

その他設定可能なプロパティがあるので、公式ドキュメントを確認してください。
https://recoiljs.org/docs/api-reference/core/selector

コンポーネントから呼び出す方法

以下の三つがあります。
基本的には名前から想像できる通りです。

  • useRecoilState
  • useRecoilValue
  • useSetRecoilState

useRecoilStateはuseStateとほぼ同じです。
引数にatomやselectorなどのRecoilStateを設定することで、stateとsetStateを得ます。

const age = atom({
  key: "age",
  default: 0
})

const Component = () => {
  const [age, setAge] = useRecoilState(age);
  return <p>{age}</p>;
}

useRecoilValueは値の参照のみ、useSetRecoilStateは更新のみになります。

非同期処理

selectorで非同期で値を取得する例が公式ドキュメントにありました。
以下はサンプルコードを引用したものですが、簡単に取得することができます。
https://recoiljs.org/docs/api-reference/core/selector/#example-asynchronous

import { selector, useRecoilValue } from 'recoil';

// selectorで非同期で値を取得
const myQuery = selector({
  key: 'MyDBQuery',
  get: async () => {
    const response = await fetch(getMyRequestUrl());
    return response.json();
  },
});

const QueryResults = () => {
  const queryResults = useRecoilValue(myQuery);
  return (
    <div>
      {queryResults.foo}
    </div>
  );
}

const ResultsSection = () => {
  return (
    <React.Suspense fallback={<div>Loading...</div>}>
      <QueryResults />
    </React.Suspense>
  );
}

エラーが起きた場合は、ErrorBoundryを定義しエラー用のコンポーネントを表示する方法が推奨されています。
https://reactjs.org/docs/error-boundaries.html

atomFamilyとselectorFamily

atomFamilyとselectorFamilyを使うことで、動的にatomやselectorを生成することができます。

例えば、APIでフェッチしてきた複数要素ある配列の値をそれぞれのコンポーネントでstateとして管理したい時に使えます。

import React, { useEffect } from "react";
import {
  atomFamily,
  DefaultValue,
  selectorFamily,
  useRecoilCallback,
  useRecoilState,
} from "recoil";

export type Item = {
  id: string;
  title: string;
  content: string;
};

type Props = {
  items: Item[];
};

// fetchしてきたデータが入ると仮定
export const Component: React.FC<Props> = ({ items }) => {
  const setState = useRecoilCallback(({ set }) => (itemValues: Item[]) => {
    itemValues.forEach((item) => {
      set(itemSelector(item.id), item);
    });
  });

  useEffect(() => {
    setState(items);
  }, [items, setState]);

  return (
    <div>
      {items.map((item, i) => {
        return <ItemComponent key={i} item={item} />;
      })}
    </div>
  );
};

const titleAtom = atomFamily<Item["title"], Item["id"]>({
  key: "title",
  default: "",
});

const contentAtom = atomFamily<Item["content"], Item["id"]>({
  key: "content",
  default: "",
});

export const itemSelector = selectorFamily<Item, Item["id"]>({
  key: "itemSelector",
  get:
    (itemId) =>
    ({ get }) => {
      return {
        id: itemId,
        title: get(titleAtom(itemId)),
        content: get(contentAtom(itemId)),
      };
    },
  set:
    (itemId) =>
    ({ set }, newValue) => {
      if (newValue instanceof DefaultValue) return;
      set(titleAtom(itemId), newValue.title);
      set(contentAtom(itemId), newValue.content);
    },
});

export const ItemComponent: React.FC<{ item: Item }> = ({ item }) => {
  // itemの数だけユニークなstateとして動的に展開される
  const [itemState, setItemState] = useRecoilState(itemSelector(item.id));
  return <p>{item.title}</p>;
};

useRecoilCallBack

useRecoilCallBackはuseCallBackと似ています。
useRecoilCallBackはメモ化したcallback関数を返して、callback関数内でstateを読み書きすることができます。
詳しくはドキュメントをご覧ください。
https://recoiljs.org/docs/api-reference/core/useRecoilCallback/

まとめ

最近reduxがめんどくさくなってきた立場として、recoilは色々可能性を感じました。
書くのも楽そうです。

調べて書いていく中で、カスタムフックで使ったり、Reactを使いこなせていないとなかなか厳しい場面もありそうだなと感じたので、普段のhooksに不安がある状態で入門するのは微妙そうです。

まだexpelimentalっぽいので、今後も注目しましょう。

参考文献

https://recoiljs.org/
https://github.com/facebookexperimental/Recoil
https://qiita.com/itachi/items/02688096bc5734396e8e
https://zenn.dev/susiyaki/articles/95dea88e673e1f854130
https://sbfl.net/blog/2020/05/17/react-experimental-recoil-usage/

GitHubで編集を提案

Discussion