RecoilはReactの状態を管理するライブラリで、Facebook改めMetaが開発しています。本稿執筆時の最新バージョンは0.7.7です。状態をひとまとめにするのではなく、ひとつひとつ細かく分けて管理します。ざっくりとお伝えするなら、useState
の状態をコンポーネントツリー内にまたがって共有するイメージです。
本稿は公式サイトの「Getting Started」で紹介されたコードサンプルを、モジュールに分けた作例に書き替えて解説します(サンプル001)。また、TypeScriptによる型づけも加えました。
サンプル001■React + TypeScript: Recoil example
React + TypeScriptのひな形アプリケーションをつくる
Reactアプリケーションのひな形は、Create React Appでつくることにします。オプションとして--template typescript
を加えれば、TypeScriptの環境が簡単に加わえられて便利です(「Create React AppでTypeScriptが加わったひな形アプリケーションをつくる」参照)。
インストール
Recoilは、npmあるいはyarnでつぎのようにインストールしてください。
npm install recoil
yarn add recoil
コンポーネントツリーを<RecoilRoot>
でつつむ
まず、状態を共有したいコンポーネントツリーのルートは、<RecoilRoot>
で包んでください。すべての子孫コンポーネントから、同一の状態が使えるようになります。ここでは、親コンポーネントをCharacterCounter
としました。
import { RecoilRoot } from 'recoil';
import { CharacterCounter } from './CharacterCounter';
export default function App() {
return (
<RecoilRoot>
<CharacterCounter />
</RecoilRoot>
);
}
atom
で状態を定める
atom()
は、状態をひとつひとつ定める関数です。<RecoilRoot>
に包まれたツリー内のすべてのコンポーネントから、同じ状態の値が読み書きできます。atom
の値を参照するコンポーネントは、暗黙的に購読対象となる仕組みです。atom
が更新されると、購読対象のコンポーネントは再描画されます。atom
に渡すオプションオブジェクトには、一意の識別子key
とデフォルト値default
を与えてください。
import { atom } from 'recoil';
export const textState = atom({
key: 'textState', // 他のatomやselectorに対して一意のID
default: '', // デフォルト値(初期値)
});
<RecoilRoot>
に包んだ親コンポーネントCharacterCounter
には、このあとふたつの子コンポーネント(TextInput
とCharacterCount
)を定めて加えます。
import { TextInput } from './TextInput';
import { CharacterCount } from './CharacterCount';
export const CharacterCounter: React.FC = () => {
return (
<div>
<TextInput />
<CharacterCount />
</div>
);
};
コンポーネントからatom
の読み書きをするために用いるのがuseRecoilState()
フックです。構文はuseState
と同じで、コンポーネントツリーの外にある状態を共有して使えます。
状態がコンポーネントの外にあることを除けば、useState
フックとやっていることは変わりません。<input type="text">
要素に入力した値が設定関数setText()
で状態を書き替え、状態変数text
の値はテキストとして表示されます。
import { useCallback } from "react";
import { useRecoilState } from 'recoil';
import { textState } from './textState';
export const TextInput: React.VFC = () => {
const [text, setText] = useRecoilState(textState);
const onChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
({ target: { value } }) => {
setText(value);
},
[setText]
);
return (
<div>
<input type="text" value={text} onChange={onChange} />
<br />
Echo: {text}
</div>
);
};
selector
で状態の値に手を加えて返す
複数のatom
から参照した値に手を加えて返すのがselector()
です。他のselector
から値を得ることもできます。派生の状態をつくる純粋な関数です。引数として渡すオプションオブジェクトのget()
に定めたコールバックは、状態の値にもとづいて処理した結果を返します。コールバックの引数(get
)のget()
がatom
(textState
)から値を参照するための関数です。
charCountState
はtextState
からtext
を得て、その文字数を返します。
import { selector } from 'recoil';
import { textState } from './textState';
export const charCountState = selector({
key: 'charCountState', // 他のatomやselectorに対して一意のID
// 状態の値にもとづいて処理した結果を返す
get: ({ get }) => {
const text = get(textState);
return text.length;
},
});
selector
の返す値を読み込むには、フックuseRecoilValue()
を用いてください。コンポーネントCharacterCount
が表示するのは、textState
に設定された文字数です。でき上がった作例の動きは、冒頭のサンプル001でご確認いただけます。
import { useRecoilValue } from 'recoil';
import { charCountState } from './charCountState';
export const CharacterCount: React.FC = () => {
const count = useRecoilValue(charCountState);
return <>Character Count: {count}</>;
};