筆者はこれまで、Reactの状態管理を行う場合は下記のものを利用してきました。
- PropsでのStateのバケツリレー
- Contextを利用したコンポーネントツリー内の受け渡し
- Reduxをもちいた一元管理で堅牢な状態管理
それぞれメリット・デメリットがあり、プロジェクトの規模や状況に応じて使い分けが必要だと感じました。
Contextのように手軽に使えてかつ、パフォーマンスが良く、Reduxのようにコードの分離が可能でかつ、学習難易度が低い、曖昧な表現ですが「ちょうどいい」状態管理ライブラリがほしいなと感じていました。そこでRecoilのことを知り、まさにその要望を満たす状態管理ライブラリなのでは無いかと思い、今回触ってみることにしました。
Recoilの特徴
Recoilは、パフォーマンスの不安を解消しつつ、コードの分離が可能で、(使うだけなら)学習難易度が低めな状態管理ライブラリです。
主に、共有するStateであるatomと、selectorと呼ばれるatomや他のselectorを受け取る純粋関数を利用します。
公式サイトのコアコンセプトページに基本的な使い方が乗っているので、それを見るとわかりやすいと思います。
この記事でも大雑把に説明します。
Atoms
const fontSizeState = atom({
key: 'fontSizeState',
default: 14,
});
atom
関数を使ってatomを作成します。
key
はその名の通りatomのキーです。他のatomと被らないように一意な値を指定します。
default
はatomが管理するStateの初期値です。
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
コンポーネントでこのStateを取得するためにはuseRecoilState
関数を利用します。引数に、先程作成したatomであるfontSizeState
を指定します。[fontSize, setFontSize]
を見てわかるように、useState
のような使い方で、コンポーネントをまたいでStateを取得・変更することができます。
Selectors
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のプロジェクトを作る
$ npx create-react-app recoil
$ cd recoil
recoilをインストール
$ npm install recoil
RecoilRoot
で大本のコンポーネントを囲う
...
import { RecoilRoot } from "recoil";
...
root.render(
<React.StrictMode>
<RecoilRoot>
<App />
</RecoilRoot>
</React.StrictMode>
);
...
atomとselectorを作成する
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を利用するコンポーネントを作成する
- テキスト入力を受け付けるコンポーネント
- 入力されたテキストの文字数を表示するコンポーネント
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コンポーネントから呼び出す
function App() {
return (
<div className="App">
<TextInput />
<CharacterCount />
</div>
);
}
これで環境を立ち上げると状態がちゃんと管理できていることがわかる
Appコンポーネントからstateを受け渡す記述もなく、コードが非常にスッキリしています。
また、Contextと違い、コードを描くときにほとんど意識することなく、無関係なコンポーネントの再レンダリングが抑えられています。
終わりに
Recoilは利用するだけなら学習難度が低く、また無駄な再レンダリングを起こすコードを書いてしまう可能性が他の状態管理ライブラリくらべて低い、非常にバランスの取れたライブラリだと感じました。これからは積極的に使っていこうと思います!