デモ(codesandbox)
今回使用したコードです → Demo
Recoilとは
meta社が開発したグローバルな状態管理をできるライブラリです。
正式リリースされたばかりだが、シンプルに状態管理をしていけるので最近注目度が高いです。
実際コードが少なく書けるので見通しもよいかと思います。
初学者でRedux使っていないので、どこがどう違って、なにが良くてなにが良くないか分からないですが。。。
インストール
まずパッケージのインストールをします。typescriptの場合は型定義もインストール忘れずに。
npm i recoil
npm i @types/recoil
RecoilRootでコンポーネントをwrapする
Recoilで状態管理する場合、そのグローバルな状態管理をする範囲をRecoilRootで囲む必要があります。プロジェクト全体を範囲とする場合、大元のをwrapします。
import { render } from "react-dom";
import { RecoilRoot } from "recoil";
import App from "./App";
//Recoilを利用するコンポーネントをRecoilRootでwrapする
const rootElement = document.getElementById("root");
render(
<RecoilRoot>
<App />
</RecoilRoot>,
rootElement
);
atomの定義
グローバルStateとして扱いたいデータをatomとして定義する。
今回は男女でフィルターかけようと思うので、このような初期値を定義します。keyは一意のものを使用してください。コピペでファイル作成したときにkeyだけ変更し忘れて同じkeyができてしまってバグになっていたことがあります。
atomの定義はこれだけでグローバルなstateとして管理できます。非常にシンプル。
import { atom } from "recoil";
type personType = { name: string; gender: string };
const person: personType[] = [
{ name: "yamada", gender: "male" },
{ name: "sato", gender: "female" }
];
//keyは一意のものを設定する
//defaultプロパティに初期値をセットするだけ
export const persons = atom({
key: "person",
default: person
});
//これでpersonsはグローバルなStateとして管理できる
atomの使い方
atomを使いたい場合はuseRecoilStateを使います。定義の仕方はuseStateと同じ。setの仕方も同じですので非常に簡単に使えます。
import { useRecoilState } from "recoil";
import { persons } from "./Atom";
export default function App() {
//useStateと同じように定義し、初期値をatomとする
const [personsArray, setPersonsArray] = useRecoilState(persons);
console.log(personsArray);
//[Object, Object]
const onClickSet = () => {
//Stateの更新もuseStateのときと同じようにできる
setPersonsArray([...personsArray, { name: "suzuki", gender: "male" }]);
};
console.log(personsArray);
// [Object, Object, Object]
return <button onClick={onClickSet}>ボタン</button>;
}
用途に分けて
const personsArray = useRecoilValue
とすると読み取り専用で定義ができます。
const setPersonsArray=useSetRecoilState
とすると書き込み専用の定義ができます。
selectorの定義
selectorはatomを加工した値をstateとして利用することができます。
atomが更新されるとselectorも更新がかかります。今回はfilter関数を使ってatomを男女に分けたselectorを作成します。
import { selector } from "recoil";
import { persons } from "./Atom";
//getで取得したpersonsをfilter関数でmaleのみ抽出
export const malesSelector = selector({
key: "males",
get: ({ get }) => {
const males = get(persons).filter((item) => {
return item.gender === "male";
});
return males;
}
});
//同様にfemaleのみ抽出
export const femalesSelector = selector({
key: "females",
get: ({ get }) => {
const females = get(persons).filter((item) => {
return item.gender === "female";
});
return females;
}
});
このようにgetプロパティ内でatomを取得して何かしらの処理で値を加工できます。
returnした値が定義したselectorの値としてグローバルに読み取ることができます。
この場合のselectorは読み取り専用となります。
setプロパティはオプションであるのですが、この記事では割愛。
selectorの読み込みとフィルター機能の実装
全体としてはこんな感じです。
(1)のようにuseRecoilValueを使ってatomのように取得できます。
あとはそれぞれの配列をmapで繰り返してリスト表示します。
どれを表示するかは別のstateを定義して、レンダリングの条件分岐などで表示を制御すれば、簡単なフィルター分けを実装できます。
条件分岐は&&を使って簡単に書いてます。
export default function App() {
//useStateと同じように定義し、初期値をatomとする
const [personsArray, setPersonsArray] = useRecoilState(persons);
console.log(personsArray);
//[Object, Object]
const onClickSet = () => {
//Stateの更新もuseStateのときと同じようにできる
setPersonsArray([...personsArray, { name: "suzuki", gender: "male" }]);
};
console.log(personsArray);
// [Object, Object, Object]
//定義したselectorはatomと同じように取得できる・・・(1)
const malesArray = useRecoilValue(malesSelector);
const femalesArray = useRecoilValue(femalesSelector);
//ボタンを押したときのshowState
const [showList, setShowList] = useState<string>("");
const onClickAll = () => {
setShowList("all");
};
const onClickMale = () => {
setShowList("male");
};
const onClickFemale = () => {
setShowList("female");
};
//レンダーの条件分岐は && を使用
//&&の左側がtrueなら右側を評価する
//map関数で配列を繰り返してリスト表示する
return (
<>
<button onClick={onClickSet}>鈴木(男)を追加</button>
<button onClick={onClickAll}>全て表示</button>
<button onClick={onClickMale}>maleのみ表示</button>
<button onClick={onClickFemale}>femaleのみ表示</button>
{showList === "all" &&
personsArray.map((item) => {
return (
<div>
<ul>
<li>{item.name}</li>
</ul>
</div>
);
})}
{showList === "male" &&
malesArray.map((item) => {
return (
<div>
<ul>
<li>{item.name}</li>
</ul>
</div>
);
})}
{showList === "female" &&
femalesArray.map((item) => {
return (
<div>
<ul>
<li>{item.name}</li>
</ul>
</div>
);
})}
</>
);
}
atomが追加や変更で更新された時のみselectorも更新されるので、その辺も特に意識する必要がなくとても簡単。
atomとselectorだけでも色々できるので、もし誰かの学習の参考になれば幸いです。