この記事はCore Concepts の翻訳記事です。 (2020/11/15時点)
Overview
Recoil を使うと、atoms(共有ステート) から selectors(純粋関数) を通してReactコンポーネントに流れていくデータフローを構築できます。
Atoms は コンポーネントがsubscribeするステートの単位で、selectors は同期的または非同期的にステートを変形します。
Atoms
atomsはステートを表す単位となるものです。
atomsは更新可能でsubscribe可能な値です。つまりatomは文字通り更新できて、それに依存しているコンポーネントは更新時に新しいatomの値で再レンダリングされます。
また、atomsはランタイムによって構築されます。atomsはReactコンポーネントのローカルなステートと同じ場所で使うことができ、もし同じatomが複数のコンポーネントで利用されているとき、コンポーネントのatomは同じステートを共有します。
atomsは atom
関数を使って作成します。
const fontSizeState = atom({
key: 'fontSizeState',
default: 14,
});
atomsの作成には一意なkeyを設定することが必要です。これはデバッグ、一貫性の維持、全てのatomsを俯瞰するためのadvanced APIなどで使われます。
もしatomsでkeyが重複する場合はエラーを起こしてしまうため、必ずこれは一意な値を使う必要があります。
またReactコンポーネントのステートと同様にデフォルト値を持ちます。
コンポーネントからatomを読み書きするためには、useRecoilState
と呼ばれるフックAPIを用います。
ReactのuseState
と見た目は似ていますが、ステートはatomsを使うコンポーネント間で共有されている点が異なっています。
function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return (
<button onClick={() => setFontSize((size) => size + 1)} style={{fontSize}}>
Click to Enlarge
</button>
);
}
この例では、ボタンをクリックするとフォントサイズがインクリメントされます。しかもこれは、他のコンポーネントのフォントサイズにも波及します。
Selectors
selectorはatomsまたは他のselectorsを入力として受け取る純粋関数です。
上流つまり入力として渡したatomsやselectorsが更新されると、それを受け取るselector関数も再評価(更新)されます。
selectorsをsubscribeしているコンポーネントは、atomsのuseRecoilState
のように、selectorが更新されたときに再レンダリングされます。
selectors は ステートに基づいて派生データを計算するために使用されます。
selectorを使うことでreducerがステートを同期して有効に保つ必要がなくなり、余計なステートが増えることが回避できます。
その代わり、atomsには最低限必要なステートのみが格納され、他のステートはそこから派生する関数、つまりはselectorによって効率的に計算されます。
selectorはコンポーネントが何に依存しているかをトラックしてくれるおかげで関数的なアプローチがより効率的に取れるようになっています。
コンポーネントから見ると、selectorもatomも全く同じインターフェースをしているため、selectorとatomはお互いに置き換え可能になっています。
selectorsはselector
関数を使って定義されます。
const fontSizeLabelState = selector({
key: 'fontSizeLabelState',
get: ({get}) => {
const fontSize = get(fontSizeState);
const unit = 'px';
return `${fontSize}${unit}`;
},
});
get
プロパティは派生するステートを計算する関数です。この関数はatomsや他のselectorsの値を get()
の引数を通して受け取ることができます。atomsやselectorsにアクセスすると依存関係が構築され、依存元が更新されたときに再計算が行われるようになっています。
今回の fontSizeLabelState
ではselectorはfontSizeState
atomにだけ依存しています。
コンセプト通り、fontSizeLabelState
selectorは fontSizeState
を入力として受け取って、フォーマットされたフォントサイズのラベルを出力する純粋関数のように振舞います。
selectorはuseRecoilValue()
を使って読み取り可能です。これは引数にatomかselectorを受け取り、それに対応する値を返すAPIです。
fontSizeLabelState
selectorは書き込み不可能なので、useRecoilState()
は使用しません。 (書き込み可能なselectorについて知りたい場合はselector API reference参照)
function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
const fontSizeLabel = useRecoilValue(fontSizeLabelState);
return (
<>
<div>Current font size: ${fontSizeLabel}</div>
<button onClick={() => setFontSize(fontSize + 1)} style={{fontSize}}>
Click to Enlarge
</button>
</>
);
}
ボタンをクリックすると、ボタンのフォントサイズが増加し、現在のフォントサイズをフォーマットして表している${fontSizeLabel}
にもその変更が反映されます。