🐥

Recoil get/setとuseRecoilStateの使い分け

2024/06/26に公開

フロントエンドの状態管理ツールの1つである Recoil。状態管理したい変数を atom や selector に定義し、get や set、reset で、取得、更新、初期化する、というのが基本的な使い方なんだと理解しました。ただ、Recoil 初心者としては、useRecoilState や useRecoilValue などの React hooks との違いがいまいちピンと来なかったので、整理しました。

Recoil とは

Recoil は React のための状態管理ライブラリで、コンポーネント間での状態共有を簡単にすることを目的としています。Recoil では、Atom と Selector、そしてそれらを利用するためのいくつかの hooks が提供されています。

https://recoiljs.org/

Atom と Selector

  • Atom: Recoil の状態管理の基礎となる概念で、管理したい変数の状態を定義するものです。Atom の値が変更されると、その Atom をウォッチしているコンポーネントが再レンダリングされます。

  • Selector: Atom や他の Selector を使って別の状態を定義したいときに使うためのものです。公式ページには「派生状態(derived state)」と記載されています。Selector は、その定義の中で利用している Atom や他の Selector の値が変更された場合に再計算されます。

上記 Atom や Selector は、getsetresetという関数を使って操作できます。

  • get: Atom や Selector の現在の値を取得します。
  • set: Atom に値を設定します。Selector においては、依存する Atom や Selector 値を設定するのに使用します。
  • reset: Atom の値を初期値にリセットします。
import { atom, selector } from "recoil";

// Atomの定義
export const textState = atom({
  key: "textState",
  default: "",
});

// Selectorの定義
export const charCountState = selector({
  key: "charCountState",
  get: ({ get }) => {
    const text = get(textState);
    return text.length;
  },
});

React Hooks

Recoil では Atom や Selector を利用するために専用の React hooks が提供されています。

  • useRecoilState: これは React のuseStateと同様、Atom の現在の状態と、その状態を更新する関数を返します。
  • useRecoilValue: Atom または Selector の現在の値のみを返します。この hook は、状態の読み取りだけで十分な場合に使われます。
  • useSetRecoilState: Atom の状態を更新する関数のみを返します。これは、現在の状態を読み取る必要はなく、更新だけしたい場合に使用します。
  • useResetRecoilState: 指定された Atom の状態を初期値にリセットする関数を返します。

以下のコードを実行してみたい場合はCodeSandboxに用意してます。

import React from "react";
import {
  RecoilRoot,
  useRecoilState,
  useRecoilValue,
  useSetRecoilState,
  useResetRecoilState,
} from "recoil";
import { textState, charCountState } from "./state";

function TextInput() {
  const [text, setText] = useRecoilState(textState);

  const onChange = (event) => {
    setText(event.target.value);
  };

  return (
    <div>
      <input type="text" value={text} onChange={onChange} />
      <p>Text: {text}</p>
    </div>
  );
}

function CharacterCount() {
  const count = useRecoilValue(charCountState);

  return <div>Character Count: {count}</div>;
}

function TextInputWithSetOnly() {
  const setText = useSetRecoilState(textState);

  const onChange = (event) => {
    setText(event.target.value);
  };

  return (
    <div>
      <input type="text" onChange={onChange} />
      <p>Set text directly</p>
    </div>
  );
}

function ResetText() {
  const resetText = useResetRecoilState(textState);

  return <button onClick={resetText}>Reset Text</button>;
}

function App() {
  return (
    <RecoilRoot>
      <TextInput />
      <CharacterCount />
      <TextInputWithSetOnly />
      <ResetText />
    </RecoilRoot>
  );
}

export default App;

違い

本記事の主題です。

  • Atom や Selector の getsetreset は主に状態の定義や更新のロジックを記述するために使用されます。直接コンポーネント内でこの3種類を呼び出し、更新のロジックを書いたりすることもできますが、hooks を使った方が便利です。
  • なぜなら useRecoilStateuseRecoilValueなどの hooks は、コンポーネント内で Recoil の状態(Atom や Selector)を他の hooks と同様の感覚でシンプルに利用できるように作られているためです。内部的に useState や useEffect と get、set などを組み合わせて、Atom や Selector を操作しているようです。useRecoilState のコードを読むとそんな雰囲気がわかると思います。

つまり、Atom や Selector の get, set, reset メソッドは状態を定義するときに利用(state 定義ファイル内でのみ利用)し、hooks はその状態を React コンポーネント内で呼び出したい時に利用する、という使い分けが良さそうです。

ただ、以下の例のように、カスタムフックを作成して、そこで get や set メソッドを使うのもありだと思います(というか、色々やりたいなら、その方が扱いやすそう)。

引用元: https://github.com/susiyaki/recoil-frontend-ddd-sample/blob/master/src/hooks/todo.ts

export const useTodo = () => {
  // NOTE: サーバからデータを取得してstateに反映するときなど
  const setFromArray = useRecoilCallback(({ set }) => (todoArray: Todo[]) => {
    todoArray.forEach((todo) => {
      set(stateTodo(todo.id), todo);
    });
  });

  const upsertTodo = useRecoilCallback(({ set }) => (newTodo: Todo) => {
    set(stateTodo(newTodo.id), newTodo);
  });

  const removeTodo = useRecoilCallback(({ set, reset }) => (todoId: TodoId) => {
    reset(stateTodo(todoId));
    set(stateTodoIds, (prev) => prev.filter((id) => id !== todoId));
  });

  return {
    setFromArray,
    upsertTodo,
    removeTodo,
  };
};

まとめ

  • シンプルに使うだけの場合は、Atom や Selector の定義内でのみ get や set を利用し、コンポーネントからは hooks を使う
  • 複雑なロジックを書きたい場合は、カスタムフックを作成して、その中で get や set を使う

Discussion