👶

Recoilで簡単に状態管理を実現しよう

2023/03/30に公開
1

初めに

今回は、状態管理ライブラリのRecoilについて解説します。状態管理ライブラリといえば、Reduxが最も有名かと思いますが、複雑なアーキテクチャーや冗長なコードが必要であると感じる人もいます。一方、Recoilはよりシンプルな設計であるため、Reduxと比較して、学習コストが低くなっています。私自身もReduxの勉強をして、あまり理解できずにいましたが、Recoilは簡単に理解できたので、皆さんに共有したいと思い、この記事を書きました。

使用ライブラリとそのバージョン

  • react==18.2.0
  • next==13.2.4
  • recoil==0.7.7

環境構築

まずは環境構築をします。

 # nextjsの雛形を作ります。
 npm create next-app study-recoil
 cd study-recoil

Recoilをインストールします。

 npm install recoil

初期設定

まずはRecoilを使うための初期設定をしましょう。

_app.tsx
import "@/styles/globals.css";
import type { AppProps } from "next/app";
import { RecoilRoot } from "recoil";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <RecoilRoot>
      <Component {...pageProps} />
    </RecoilRoot>
  );
  }

_app.tsx <Component {...pageProps} /><RecoilRoot>で囲むだけです。
これだけで、useRecoilStateuseRecoilValue(後述)などのRecoil Hooksが利用可能になります。

状態を定義

Recoilでは、状態はatomという概念で表現されます。atomは、一意のキー(key)と初期値(default)を持つJavaScriptオブジェクトです。

 # 状態を管理するためのファイルをsrc/state配下に作成
 mkdir src/state
 touch src/state/textState.ts

keyは状態の名前であり、アプリケーション内で一意である必要があります。
defaultには状態の初期値を設定します。

src/state/textState/ts
import { atom } from "recoil";

export const textState = atom({
 key: 'textState', 
 default: '',
});

状態の読み書き

状態を定義できたので、その値を読み書きしてみましょう。
状態を読み書きするためには、useRecoilStateというHookを使用します。

src/pages/index.tsx
import { useRecoilState } from "recoil";
import { textState } from "../state/textState";
import { ChangeEvent } from "react";

export default function Home() {
  const [text, setText] = useRecoilState(textState);

  function handleChange(e: ChangeEvent<HTMLInputElement>) {
    setText(e.target.value);
  }
  return (
    <div>
      <input type="text" value={text} onChange={handleChange} />
      <p>You entered: {text}</p>
    </div>
  );
}

6行目のコードが状態を読み書きするためのHookです。
useRecoilStateの引数には、先ほど作成したatomを指定します。
Reactを使ったことがある方は「useStateに似ているな」と思ったのではないでしょうか。
使い方自体はuseStateと同じです。
npm run devで開発サーバーを起動して確認してみましょう。

defaultの値を空文字にしたので You entered: の後には何も表示されていません。

inputボックスに文字を打ち込めば状態が更新されていることがわかります。

状態の読み取り

useRecoilValueというのも存在します。これは、useRecoilStateと違い状態のみを取得することができます。

 touch src/pages/text.tsx

別のページを作成します。

src/pages/text.tsx
import { textState } from "../state/textState";
import { useRecoilValue } from "recoil";
import Link from "next/link";

export default function Text() {
  const text = useRecoilValue(textState);

  return (
    <div>
      <p>You entered: {text}</p>
      <Link href="/">ホームへ</Link>
    </div>
  );
}

useRecoilValueの第一引数にもatomを指定します。
画面を遷移するためのLinkも設置しておきました。

src/pages/index.tsx
// index.tsxからtext.tsxへ移動するためのLinkを設置
<Link href="/">textへ</Link>

画面で確認しましょう。


inputボックスに文字を入力し、画面を遷移してもtextは保持されたままです。

更新関数のみを取得できるuseSetRecoilStateも存在しますが、この記事では説明を省略します。

計算された状態を定義

Recoilでは、複数のatomを組み合わせて計算された状態を定義することもできます。

src/state/textState.ts
import { atom, selector } from "recoil";

export const charCountState = selector({
  key: 'charCountState',
  get: ({ get }) => {
    const text = get(textState);
    return text.length;
  },
});

selectorとはatomと同じような構造のオブジェクトであり、getプロパティに関数を指定して計算された状態を定義できます。
get関数の第一引数には使用したいatomselectorを指定します。
今回の場合は先ほど定義したtextStateの文字数をカウントして返すselectorを作成しました。

計算された状態を使用

selectorは状態を返すだけなので、更新関数は存在しません。
よってuseRecoilValueで値の取得ができます。

import { useRecoilState, useRecoilValue } from "recoil";
import { charCountState, textState } from "../state/textState";
import { ChangeEvent } from "react";
import Link from "next/link";

export default function Home() {
  const [text, setText] = useRecoilState(textState);
  const charCount = useRecoilValue(charCountState); // 追加

  function handleChange(e: ChangeEvent<HTMLInputElement>) {
    setText(e.target.value);
  }
  return (
    <div>
      <input type="text" value={text} onChange={handleChange} />
      <p>You entered: {text}</p>
      <p>Character count: {charCount}</p> // 追加
      <Link href="/text">textへ</Link>
    </div>
  );
}

画面で確認しましょう。

この通りinputボックスに入力された値の文字数がカウントされています。

selectorを使用するメリット

selectorの説明をみて「useRecoilStateで取得したstatestate.lengthとすれば良いのではないか」と感じた人もいると思います。
selectorを使用する主なメリットは、複数のatomから取得されたデータを元に、より複雑な状態を計算できることです。また、メモ化された関数を提供するため、複数回呼び出される場合でも、最初の呼び出しで計算された結果を返します。さらに、コードをシンプルにし、アプリケーション全体で共有される状態の複雑さを減らすことができます。

TypeScript対応

最後にTypeScriptで型をつけていきましょう。

src/state/textState.ts
import { atom, selector } from "recoil";

export const textState = atom<string>({
  key: "textState",
  default: "",
});

export const charCountState = selector<number>({
  key: "charCountState",
  get: ({ get }) => {
    const text = get(textState);
    return text.length;
  },
});

atomにはdefaultの型、selectorにはreturnされる値の型を指定します。

まとめ

以上が、Recoilの基本的な使い方についての説明です。Recoilを使用することで、簡単かつ柔軟な状態管理を実現することができます。また、ReactのHooksを利用するため、学習コストも低くなっています。

始めて記事を書いたので拙い文章だったかもしれませんが、参考になったと思ったら、いいねを押していただけると幸いです。

Discussion