Reactの状態管理ライブラリ「Recoil」の簡単な使い方

Recoilとは

RecoilはFacebookによって開発されたReactアプリケーションのための状態管理ライブラリです。

Reactで状態管理をする場合はReactのuseContext (createContext)やReduxというFluxアーキテクチャに基づいた状態管理ライブラリが使用されることが多いです。

Recoilは前述の2つと比べると新しいライブラリなので、使用されているケースは多くはないですが、次のような特徴とメリットがあるため、使用されるケースが増えています。

1. 原子性のある状態

Recoilでは、アトム(Atoms)と呼ばれる原子的な状態を定義します。

これにより、アプリケーション全体での状態の一貫性と予測可能性が向上します。

2. 派生状態とセレクター

Recoilでは、状態から派生する他の状態を定義することができます。

これにより、コンポーネント内で複雑な状態ロジックを簡単に作成できます。

3. 非同期サポート

Recoilは非同期操作をサポートしており、非同期データの取得や更新にも適しています。

4. 柔軟性と拡張性

RecoilはReactのフックと統合されており、既存のReactコンポーネントとシームレスに統合できます。

また、Recoilのアトムは関数コンポーネント内で動的に定義することも可能です。

5. パフォーマンス

Recoilは、Reactのコンポーネントツリーの再レンダリングを最小限に抑えるための最適化が施されています。

これにより、大規模なアプリケーションでもパフォーマンスを維持しながら状態を管理できます。

Recoilの使い方

この記事ではRecoilを使用してReactの状態管理を実装するための方法について説明しています。

まず、以下のコマンドでReactの開発環境を作成してください。

npm create vite@latest my-recoil -- --template react

コマンド実行後にcdでmy-recoilに移動して、インストールを実行すればReactの環境構築は完了です。

cd my-recoil
npm install

今回はRecoilを使用するので、以下のコマンドでRecoilもインストールしてください。

npm i -D recoil

ここまで完了したら、App.jsxを開いて、recoilからRecoilRootをimportして以下のように修正します。

Recoilを使用する箇所は<RecoilRoot>で囲む必要があります。

App.jsx
import { RecoilRoot } from 'recoil'

function App() {
  return (
    <RecoilRoot>
      <h1>Recoil Sample</h1>
      <div>
        <input type="text" value="" /><br />
        Text:
      </div>
      <div>
        Length:
      </div>
    </RecoilRoot>
  )
}

export default App

今回はinputに入力すると、Text:に入力したテキスト、Length:に文字数が表示されるサンプルをRecoilを使用して作成します。(JSの処理はこのあと追加)

index.cssは使用しないので、main.jsxの4行目の「import './index.css'」は削除してください。

いったん、この状態で「npm run dev」を実行すると、下図のような状態で表示されます。

npm run dev

コンポーネントを作成する

今回のサンプルではCharacterCounter.jsx, CharacterCount.jsx, TextInput.jsxという3つのファイルを学習のために作成します。

まず、以下のコマンドでsrc直下にcomponentsディレクトリを作成して、前述の3つのjsxファイルを作成します。

mkdir src/components; touch src/components/CharacterCounter.jsx src/components/CharacterCount.jsx src/components/TextInput.jsx

jsxファイルを新規作成したら、4つのjsxファイルを修正して、コンポーネントを読み込めるようにします。

App.jsx
import { RecoilRoot } from 'recoil'
import { CharacterCounter } from './components/CharacterCounter'

function App() {
  return (
    <RecoilRoot>
      <CharacterCounter />
    </RecoilRoot>
  )
}

export default App
CharacterCounter.jsx
import { TextInput } from './TextInput'
import { CharacterCount } from './CharacterCount'

export function CharacterCounter() {
  return (
    <div>
      <h1>Recoil Sample</h1>
      <TextInput />
      <CharacterCount />
    </div>
  )
}
TextInput.jsx
export function TextInput() {
  return (
    <div>
      <input type="text" value="" /><br />
      Text:
    </div>
  )
}
CharacterCount.jsx
export function CharacterCount() {
  return (
    <div>
      <div>
        Length:
      </div>
    </div>
  )
}

コンポーネントにわけて作成しても、npm run devで確認すると、先ほどと同じ見た目で表示されています。

Recoilのサンプル

状態管理(state)のファイルを作成する

状態管理のファイル(src/state/TextState.jsx)を以下のコマンドで作成します。

mkdir src/state; touch src/state/TextState.jsx

TextState.jsxでatomをインポートして、どのコンポーネントからもデータにアクセスできるようにします。

TextState.jsx
import { atom } from 'recoil'

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

keyは任意の名前で、defaultには初期値を設定します。

TextState.jsxはcomponentsではなくstateディレクトリに入れていますので、間違えないよう注意が必要です。

状態管理(state)のファイルを作成する

TextInput.jsxにTextState.jsxをimport

TextState.jsxを作成したらTextInput.jsxでimportし、useRecoilStateを使用してテキスト入力時にText:の右にテキストが表示されるようにします。

※ useRecoilStateはuseStateに似ていますが、useStateではtextStateは扱えません。

TextInput.jsx

TextInput.jsx
import { useRecoilState } from 'recoil'
import { textState } from '../state/TextState'

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

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

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

ここまで作成したら、npm run devでページを表示して確認してみてください。

inputにテキストを入力すると、Text:の右にテキストが反映されます。

試しにatomのdefault値を空文字ではなく、sampleに変更すると、最初はsampleという文字列が表示されます。

TextState.jsx
import { atom } from 'recoil'

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

CharacterCount.jsxにTextState.jsxをimport

TextInput.jsxと同様にCharacterCount.jsxにもTextState.jsxをimportします。

TextState.jsx
import { atom } from 'recoil'

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

CharacterCount.jsxではテキストの文字数を返すため、recoilからuseRecoilStateではなく、selectorと useRecoilValueをimportします。

selectorはAtomのデータを処理する関数を定義します。

useRecoilValueはselectorで定義した値を取得するために使用します。

CharacterCount.jsx
import { selector, useRecoilValue } from 'recoil'
import { textState } from '../state/TextState'

const charCountState = selector({
  key: 'charCountState',
  get: ({ get }) => {
    const text = get(textState)

    return text.length
  },
})

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

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

useRecoilStateでも作れなくはないですが、setTextが使われないので、今回の文字数をカウントするケースでの使用は好ましくないです。

CharacterCount.jsx
// 以下は非推奨
import { useRecoilState } from 'recoil'
import { textState } from '../state/TextState'

export function CharacterCount() {
  const [text, setText] = useRecoilState(textState)
  const count = text.length

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

ここまで作成した状態でWebページを確認すると、テキストの文字数カウントも表示されることが確認できます。

Recoilを使用したサンプル

Recoil 公式サイト

まとめ

以上のように、Recoilを使用すればReactの状態管理が簡単に扱えて、propsのバケツリレーを解消することができます。

RecoilはFacebookによって開発されており、今後廃れる可能性が低いので、propsのバケツリレーが発生して、状態管理の方法で迷ったらRecoilを使用すると良いでしょう。