便利な状態管理ライブラリRecoilを使ってみる

筆者はこれまで、Reactの状態管理を行う場合は下記のものを利用してきました。

- PropsでのStateのバケツリレー
- Contextを利用したコンポーネントツリー内の受け渡し
- Reduxをもちいた一元管理で堅牢な状態管理

それぞれメリット・デメリットがあり、プロジェクトの規模や状況に応じて使い分けが必要だと感じました。

Contextのように手軽に使えてかつ、パフォーマンスが良く、Reduxのようにコードの分離が可能でかつ、学習難易度が低い、曖昧な表現ですが「ちょうどいい」状態管理ライブラリがほしいなと感じていました。そこでRecoilのことを知り、まさにその要望を満たす状態管理ライブラリなのでは無いかと思い、今回触ってみることにしました。

## Recoilの特徴

Recoilは、パフォーマンスの不安を解消しつつ、コードの分離が可能で、(使うだけなら)学習難易度が低めな状態管理ライブラリです。

主に、共有するStateであるatomと、selectorと呼ばれるatomや他のselectorを受け取る純粋関数を利用します。

公式サイトのコアコンセプトページに基本的な使い方が乗っているので、それを見るとわかりやすいと思います。

https://recoiljs.org/docs/introduction/core-concepts

この記事でも大雑把に説明します。

### Atoms

```javascript
const fontSizeState = atom({
  key: 'fontSizeState',
  default: 14,
});
```

`atom`関数を使ってatomを作成します。

`key`はその名の通りatomのキーです。他のatomと被らないように一意な値を指定します。

`default`はatomが管理するStateの初期値です。

```javascript
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
```

コンポーネントでこのStateを取得するためには`useRecoilState`関数を利用します。引数に、先程作成したatomである`fontSizeState`を指定します。`[fontSize, setFontSize]`を見てわかるように、`useState`のような使い方で、コンポーネントをまたいでStateを取得・変更することができます。

### Selectors

```javascript
const fontSizeLabelState = selector({
  key: 'fontSizeLabelState',
  get: ({get}) => {
    const fontSize = get(fontSizeState);
    const unit = 'px';

    return `${fontSize}${unit}`;
  },
});
```

`selector`関数を使ってselectorを作成します。

`key`は同じく一意な値である必要があります。

`get`は実行される関数です。`get`を引数にとっていますが、このget関数を利用することで他のatomやselectorにアクセスできるようになります。

アクセスしているatomやselectorが更新された場合、`get`は再計算され最新の値となります。


### 実際につかってみる

まずはReactのプロジェクトを作る

```sh
$ npx create-react-app recoil
$ cd recoil
```

recoilをインストール

```sh
$ npm install recoil
```

`RecoilRoot`で大本のコンポーネントを囲う

```react:index.js
...
import { RecoilRoot } from "recoil";
...
root.render(
  <React.StrictMode>
    <RecoilRoot>
      <App />
    </RecoilRoot>
  </React.StrictMode>
);
...
```

atomとselectorを作成する

```react:App.js
import { atom, selector, useRecoilState, useRecoilValue } from "recoil";

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

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

    return text.length;
  },
});
...
```

それぞれrecoilのatom, selectorを利用するコンポーネントを作成する

- テキスト入力を受け付けるコンポーネント
- 入力されたテキストの文字数を表示するコンポーネント

```react:App.js
function TextInput() {
  const [text, setText] = useRecoilState(textState);

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

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

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

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

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

```react:App.js
function App() {
  return (
    <div className="App">
      <TextInput />
      <CharacterCount />
    </div>
  );
}
```

これで環境を立ち上げると状態がちゃんと管理できていることがわかる

![スクリーンショット 2022-07-12 19.43.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215011/54cab461-f3ae-8928-b493-a16cbe372e37.png)

Appコンポーネントからstateを受け渡す記述もなく、コードが非常にスッキリしています。

また、Contextと違い、コードを描くときにほとんど意識することなく、無関係なコンポーネントの再レンダリングが抑えられています。

## 終わりに

Recoilは利用するだけなら学習難度が低く、また無駄な再レンダリングを起こすコードを書いてしまう可能性が他の状態管理ライブラリくらべて低い、非常にバランスの取れたライブラリだと感じました。これからは積極的に使っていこうと思います！