なにかと話題になるRecoilですが、未だ触ったことがなかったので公式ドキュメントを読みつつ、入門してみたいと思います。
本記事の目的
-
Recoilの公式ドキュメントの
Introduction
とBasic Tutorial
を参考に、Atom、Selectorの基本を学ぶ
本記事で扱わないこと
- Recoilの応用
- Recoilの運用や設計
- 他ライブラリとの比較
Recoilとは
RecoilはReactの状態管理ライブラリです。Reactにおいてグローバルな状態をどう管理するかというのは悩ましい話題の一つです。ビルドインで備わっている状態管理機構(Context APIやpropsのバケツリレー)もありますが、それはそれでいくつかの制約があります(詳しくはドキュメントのMotivationをご参照ください)。Recoilは、そのような問題を改善するために開発されているという背景があるようです。
Recoilに入門する
Recoilの重要な概念としてAtomとSelectorが存在します。本記事ではこのAtomとSelectorの基本について学んでいきます。
RecoilRoot
まず最初の準備としてRecoilを使用するためには、どこかの祖先・親コンポーネントでRecoilRoot
を使用する必要があります。
import { RecoilRoot } from 'recoil';
import { CharacterCounter } from './components/CharacterCounter';
const App = () => {
return (
<RecoilRoot>
{/* この中でRecoilが使える */}
<CharacterCounter />
</RecoilRoot>
);
};
export default App;
Atom
Atomは状態の一部を表します。Atomは任意のコンポーネントから状態の読み書きをすることができます。あるコンポーネントからAtomの状態を書き換えると、そのAtomを読み込んでいるすべてのコンポーネントが再レンダリングされ、書き込んだ状態が反映されます。いわゆるグローバルな状態をAtomに保持することが可能です。
import { atom } from 'recoil';
const textState = atom({
key: 'textState', // ユニークなID
default: '', // デフォルト値
});
key
にはユニークなID(他のAtomやSelectorと被らない値)を設定する必要があります。default
にデフォルト値を設定することができます。
Atomへのアクセス
コンポーネントからAtomにアクセスするためにはフックを使う必要があります。
状態の読み取り
Atomから状態を読み取るにはuseRecoilValue
フックを使うことができます。次の例ではEcho: Hello
の文字列が表示されると思います。
import { atom, useRecoilValue } from 'recoil';
const textState = atom({
key: 'textState',
default: 'Hello',
});
export const CharacterCounter = () => {
return (
<div>
<TextInput />
</div>
);
};
const TextInput = () => {
// useRecoilValue()の引数にAtomを渡す
const text = useRecoilValue(textState);
return <div>Echo: {text}</div>;
};
状態の書き込み
Atomへ状態を書き込むにはuseSetRecoilState
を使うことができます。次の例ではinput
の値を書き換えることによって、Atomの値が更新されることを確認できます。
import { atom, useRecoilValue, useSetRecoilState } from 'recoil';
const textState = atom({
key: 'textState',
default: 'Hello',
});
export const CharacterCounter = () => {
return (
<div>
<TextInput />
</div>
);
};
const TextInput = () => {
const text = useRecoilValue(textState);
// useSetRecoilState()の引数にAtomを渡す
const setText = useSetRecoilState(textState);
return (
<div>
<input type="text" value={text} onChange={(e) => { setText(e.target.value) }}
/>
<br />
Echo: {text}
</div>
);
};
状態の読み取りと書き込み
Atomから状態を読み取る、Atomへ状態を書き込む、両方の操作を行うフックとしてuseRecoilState
を使うこともできます。useRecoilState
の見た目はuseState
に似ているので、非常にとっつきやすいと思います。ただし引数に初期値ではなく、Atomを渡します。
import { atom, useRecoilState } from 'recoil';
const textState = atom({
key: 'textState',
default: 'Hello',
});
export const CharacterCounter = () => {
return (
<div>
<TextInput />
</div>
);
};
const TextInput = () => {
// useRecoilState()の引数にAtomを渡す
const [text, setText] = useRecoilState(textState);
return (
<div>
<input type="text" value={text} onChange={(e) => { setText(e.target.value) }}
/>
<br />
Echo: {text}
</div>
);
};
Selector
ドキュメントによるとSelectorは「derived state」と記述されていました。翻訳にかけてみると「派生した状態」という意味になるようです。
派生した状態とは「transformation」、つまり何かしらのAtomを変換・加工した値のことです。Selectorは純粋関数でAtom(および他のSelector)を変換・加工した出力と捉えることができます。
import { selector } from 'recoil';
const charCountState = selector({
key: 'charCountState', // ユニークなID
get: ({get}) => {
// 文字列を取得し、文字列の長さを返す
const text = get(textState);
return text.length;
},
});
Atomと同様にkey
にはユニークなIDを設定する必要があります。上記の例ではget
を設定しています。get
に渡された関数はAtomおよび他のSelectorにアクセスできます。
Selectorへのアクセス
Atomと同様に、Selectorへのアクセスもフックを使う必要があります。
Selectorから派生状態を取得する
派生状態の取得にはuseRecoilValue
を使用することができます。ここでAtomの状態の読み取りでもuseRecoilValue
を使用したことに気づくかもしれません。そうです、Atomの読み取りと同様のフックを使用して、Selectorの派生状態を取得することができます。
import { atom, selector, useRecoilState, useRecoilValue } from 'recoil';
const textState = atom({
key: 'textState',
default: 'Hello',
});
const charCountState = selector({
key: 'charCountState',
get: ({get}) => {
const text = get(textState);
return text.length;
},
});
export const CharacterCounter = () => {
return (
<div>
<TextInput />
<CharacterCount />
</div>
);
// ...略
const CharacterCount = () => {
// useRecoilValue()の引数にselectorを渡す
const count = useRecoilValue(charCountState);
return <>Character Count: {count}</>;
}
Selectorを通して状態を更新する
今回のコードでは登場しませんが、Selectorを通して加工した状態を書き込むことも可能です。ただし、Atomとは違い、Selectorは常に書き込み可能というわけではありません。
書き込み可能なセレクターを生成するためには、set
プロパティを設定する必要があります。
const proxySelector = selector({
key: 'ProxySelector',
get: ({get}) => ({...get(myAtom), extraField: 'hi'}),
set: ({set}, newValue) => set(myAtom, newValue),
});
Atomと同様に、書き込み可能なSelectorはuseSetRecoilState
やuseRecoilState
を使用して、加工した状態を書き込むことができます。
おわりに
Recoilに入門してみるということで、AtomとSelectorの基本的な部分に関してドキュメントを読みつつまとめてみました。本記事で取り上げた内容は基本中の基本であり、Recoilのほんの一部しかすぎませんが、ドキュメントも読みやすく、APIもシンプルで、非常に使いやすく感じました。気になっているけどまだ触ったことないという方は、是非触ってみてください!