はじめに
状態管理の採用で、jotai・Zustand・useReducer・Recoilなど技術選定していて調査していたので、公式のドキュメントを読みながらメモ。
Recoil自体はfacebookexperimental/Recoilなので、早くメジャーリリースをしてほしいですね。
目次
インストール
Installationを参考に各パッケージマネージャーに対応したコマンドを入力してインストール
注意
RecoilビルドはES2015に変換されず、ES2015での使用はサポートされていないっぽい。
レガシーな環境をサポートすることは少ないが、使用に関しては念頭に置いておく必要がある。
ESLint
eslint-plugin-react-hooksを使用している場合、ドキュメント通りにconfigを修正。
また、additionalHooksのリストに「useRecoilCallback」を追加することでuseRecoilCallbackに渡された依存関係が正しくない場合に、warningとして警告されるようになる。
セットアップ
RecoilRootを設置
ルートコンポーネントにReacoilRootを設定。
import type { NextPage } from "next";
import type { AppProps } from "next/app";
import { RecoilRoot } from "recoil";
const App: NextPage<AppProps> = ({ Component, pageProps }) => {
return (
<RecoilRoot>
<Component {...pageProps} />
</RecoilRoot>
);
};
export default App;
Atom
- key: atomに一意のkeyを与える(この文字列はアプリケーション全体でユニークである必要がある)
- default: default値
- effects: Atom Effectsのオプション配列
- dangerouslyAllowMutability: イミュータブルなオブジェクトの変更を許可するかどうかのフラグ
import { atom } from 'recoil';
export const counter = atom({
key: 'myCounter',
default: 0,
});
- 定義したAtomへ書き込みと読み取りを双方行う場合は、useRecoilStateフックを使用する
import { FC } from 'react';
import { useRecoilState } from 'recoil';
import { counter } from '@/stores/counter'
type Props = {};
export const Counter: FC<Props> = () => {
const [count, setCount] = useRecoilState(counter);
const incrementByOne = () => setCount((count) => count + 1);
return (
<div>
Count: {count}
<br />
<button onClick={incrementByOne}>Increment</button>
</div>
);
}
Selector
- key: selectorに一意のkeyを与える(この文字列はアプリケーション全体でユニークである必要がある)
- get: ゲッターを定義、PromiseやLoadable、また別のAtomやSelectorを返すことが可能
- set: セッターを定義
- dangerouslyAllowMutability: イミュータブルなオブジェクトの変更を許可するかどうかのフラグ
- cachePolicy_UNSTABLE: 内部セレクターキャッシュの動作を定義、現在はevictionのみらしい。
const proxySelector = selector({
key: 'ProxySelector',
get: ({get}) => ({...get(myAtom), extraField: 'hi'}),
set: ({set}, newValue) => set(myAtom, newValue),
});
import { FC, Suspense } from 'react';
import { selector, useRecoilValue } from 'recoil';
const myQuery = selector({
key: 'MyDBQuery',
get: async () => {
const response = await fetch('データ取得用のAPI');
return response.json();
},
});
type QueryResultsProps = {};
const QueryResults: FC<QueryResultsProps> = () => {
const queryResults = useRecoilValue(myQuery);
return (
<div>
{queryResults.foo}
</div>
);
};
type ResultsSectionProps = {};
const ResultsSection: FC<ResultsSectionProps> = () => {
return (
<Suspense fallback={<div>Loading...</div>}>
<QueryResults />
</Suspense>
);
};
まとめ
AtomとSelectorについて記載。
selectorが非同期処理と組み合わせることができて、Suspenseと親和性が高い設計になっているので使いやすいと感じた(SuspenceはPromiseをthrowするとfallbackコンテンツを表示するが、selector側でもそのような実装になっているため)
次回はAtomFamily、selectorFamily、Loadableあたりについて記載します。