今回は業務の一環でRecoilについて学習する機会があったので、その内容を初心者向けに解説した記事になります。
なお、筆者はReactフロントエンドの開発歴が浅い初学者ですので理解や書き方に至らぬ点があるかもしれません。
もし誤りを見つけたらTwitter等で優しく教えていただけると非常にありがたいです。
対象読者
- React初学者
- これからRecoilを学習する方
ざっくり言うと
Recoilは、ReactのuseStateみたいな状態を保持するhookを、コンポーネントを跨いで使えるライブラリです。
Recoilとは?
具体的にRecoilの内容について見ていきましょう。
Recoilとは、Context APIが抱える制約・問題を解決するためにFacebookによって提唱された実験的な状態管理ライブラリです。
Recoilでは、「Atom(共有状態)」と「Selector(純粋関数)」というものを利用してアプリケーションデータを管理します。
AtomからSelectorを経て、Reactコンポーネントへと流れるデータフローをHooks APIで操作しながら状態管理を行います。
Context APIの制約
ReactはRecoilを使わずとも元々ステート管理機能を有しています。
特にContext APIはRecoil同様、コンポーネントを跨いで使うことができます。
一般的に互換性とシンプルさの理由から、外部のグローバルステートではなくReactの組み込みステート管理機能を使用するのがベストとされています。
しかし上記でも書いた通りReactには一定の制約があります。
- ツリー全体の再レンダリングが必要になる可能性がある
Context APIで管理されているReactコンポーネントの状態を変更するためには祖先コンポーネントまで辿ってツリーを更新しなくてならないため、オブジェクトに依存する一連のコンポーネントが全てレンダリングされてしまいます。
その為、大規模になればなるほどパフォーマンスの問題が発生してしまいます。
- Contextは単一の値しか保存できない
Context変数は、一度に1つの値のみを保持するように設計されています。
そのためそれぞれが独自のコンシューマを持つ不特定多数の値を保存することはできません。
上記のような制約をAPIとセマンティックと動作の全てをできるだけReact的なものに保ちながら、これを改善したいと開発されたのがRecoilなわけです。
Reduxについて
状態管理ツールで最も人気なものとしてReduxについても簡単に触れておきます。
Reduxの特徴は以下の通りです。
メリット
-
状態変化が追いやすい
Recoil同様、Fluxフローの概念に基づき作成されておりデータの流れが一方向になっており、状態変化が追いやすい作りになっています。 -
対象コンポーネントのみレンダリングされる
管理している値が変化すると、対象のコンポーネントのみ再レンダリングされるので、Context APIのような問題は発生しません。 -
ドキュメントが豊富
React および React Nativeの分野で非常に人気があり、ディファクスタンダードである分、参考になるドキュメントが豊富です。
デメリット
- 手続きが非常に多く面倒
Reactと切り離して状態を管理しますが、Reduxだけで新たに5つの要素(Container・Action・Reducer・Store・Conncect)が存在しています。
その為、コードの記述量が増えたり、ファイル数が増えてしまうのが避けらず、手軽に導入しずらい面があります。
比較してみて
RecoilはReduxに並ぶ性能を持ちつつ、APIやアプリケーションの本質とは違うボイラープレートがReduxよりずっと少ないため記述量を必要としません。
管理しやすいのは勿論のこと、非常に導入しやすいライブラリだと言えると思います。
Reduxは長い間状態管理ライブラリの代表としての地位を確立されていたことから、すぐにシェアが減る様なことはないでしょうが、Reactに取り込まれるライブラリとしてRecoilが本命視されつつあるとの意見も多い様です。
Recoilの使用方法
実際にRecoilを導入する流れについても簡単に紹介していきたいと思います。
準備①: インストール
最新の安定版をインストールするには、ターミナルで次のコマンドを実行します。
npm install recoil
準備②: RecoilRootで囲む
リコイルステートを使用するコンポーネント際はまず、App.jsx等のルートコンポーネントをRecoilRootで囲む必要があります。
import React from 'react';
import { RecoilRoot } from 'recoil';
function App() {
return (
<RecoilRoot>
<CharacterCounter />
</RecoilRoot>
);
}
Atom
1. Atomを宣言する
Atomでステートを作ります。
Atomは、どのコンポーネントからでも読み書きができ、宣言はコンポーネントの外で宣言する必要があります。
const textState = atom({
key: 'textState', // ユニークなID
default: '', // 初期値
});
2. コンポーネント内で値を取得して表示する
値を取得するには以下のようにuseRecoilState()を使用する必要があります。
function CharacterCounter() {
return (
<div>
<TextInput />
<CharacterCount />
</div>
);
}
function TextInput() {
const [text, setText] = useRecoilState(textState);
const onChange = (event) => {
setText(event.target.value);
};
return (
<div>
<input type="text" value={text} onChange={onChange} />
<br />
Echo: {text}
</div>
);
}
Selecter
1. selectorの宣言
Selectorsは複数のAtom・他のSelectorを受け取る純粋な関数として定義し、他のデータに依存する動的データを構築できます。
const charCountState = selector({
key: 'charCountState', // ユニークなID
get: ({get}) => {
const text = get(textState);
return text.length;
},
});
この例ではgetしか作っていないので読み取り専用のselectorになります。
setも定義すれば、複数のatomの値をまとめて更新するようなsetterを持つselectorになります。
2. selectorの値を取得
useRecoilValue()フックを使って、charCountStateの値を読み取ることができます。
function CharacterCount() {
const count = useRecoilValue(charCountState);
return <>Character Count: {count}</>;
}
Recoilの利用について
2022年9月現在、RecoilはまだMeta Experimentalで開発されています。
中の人曰く、Meta社はRecoilをすでにプロダクションで使っているが、React18で追加されたConcurrent Rendering、Server Components、Streaming SSRなど、React本体の大きな新機能すべてにRecoilが互換性を保てると確信が持てるまではexperimentalのままでいたいという方針だそうです。
私は今回学習してみて個人的にRecoilは是非使って見たいと感じました。
先に書いた通り、APIやアプリケーションの本質とは違うボイラープレートが少ないことが、書きやすさや管理のしやすさとしてとても魅力を感じました。
またRecoilの基本機能はすでに安定しているようで、AtomやSelectorといった中心的なAPIの破壊的変更はほぼありませんし、すでに世界中で多くの人がプロダクションに使っていて特に大きな問題は見つかっていないない様です。
将来に渡ってメンテしていくようなプロダクトであればRecoilを利用を検討してみるのは良いのでは無いでしょうか。