Recoil 素振り
はじめに
こんにちは、かべです。今日は話題の(?)状態管理ライブラリ Recoil を使って遊んでみようと思います。たぶん流行りには乗り遅れてますが、その辺は気にしない。
公式の todo アプリを作るチュートリアルがあるのですが、今回はもう少し簡単にさくっとアプリを作っていこうと思います。使う機能はほとんど変わりません。実装したい機能としては、
- 好きな数字を選んでリストに追加し、表示する
- 表示する数字を偶数か奇数かでフィルタリング出来る
完成イメージはこのようになります。
では、早速作って行きましょう。コードはこちらに置いてあります。
環境
今回の開発は以下の環境で行いました。
- Recoil 0.2.0
- Ubuntu 20.04.1
- yarn 1.22.5
- React 17.0.2
- TypeScript 4.2.4
React アプリの作成、Recoil の導入
まずは npx create-react-app [好きな名前] --template typescript
を叩いて React アプリを作成します。完成したら、yarn add recoil
で Recoil を入れたり、使わない /public
の画像等整理してあげたりしましょう。
まずは Recoil で状態管理が出来るように設定を行います。状態管理をしたい部分だけ<RecoilRoot></RecoilRoot>
で囲めばよいのですが、今回は規模が小さいので雑に src/index.tsx
に書きます。
src/index.tsx
import React from "react"; import ReactDOM from "react-dom"; import { RecoilRoot } from "recoil"; import App from "./App"; ReactDOM.render( <React.StrictMode> <RecoilRoot> <App /> </RecoilRoot> </React.StrictMode>, document.getElementById("root") );
atom, selector の作成
Recoil では atom と呼ばれるもので状態を持ち、selector と呼ばれるものでそれを分類したり変形した値を返したりすることが出来ます。
今回必要になる atom や selector の内容としては、次のようになります。
- 追加された数字を持つ配列
- どのような数字を表示するか(偶数、奇数、全て)
selector
- 数字の配列から指定された条件(偶数、奇数、全て)を満たす数字のみからなる配列
これらを src/recoil/
というディレクトリの中に作っていきます。atom はアプリ全体で一意の key とデフォルトの値を持ち、次のように書くことが出来ます。
src/recoil/atoms.ts
import { atom } from "recoil"; export const numbersState = atom({ key: "numbersState", default: [] as number[], }); export const numbersFilterState = atom({ key: "numbersFilterState", // "all", "odd", "even" が入る default: "all", });
selector はアプリ全体で一意の key と、他の atom や selector の値から求められる値を返す get などからなります。他にも set などのオプションもありますが、今回は使いません。今回使用する selector は次のように書くことが出来ます。
src/recoil/selectors.ts
import { selector } from "recoil"; import { numbersState, numbersFilterState } from "./atoms"; export const filteredNumbersState = selector({ key: "filteredNumbersState", get: ({ get }) => { const filter = get(numbersFilterState); const numbers = get(numbersState); switch (filter) { case "odd": return numbers.filter((number) => number % 2 !== 0); case "even": return numbers.filter((number) => number % 2 === 0); default: return numbers; } }, });
atoms から取ってきた値を filter と numbers に入れ、switch 文で返り値を分岐させています。
コンポーネント内での使用
atom と selector が整ったので、実際にアプリ内で使用していきます。まずは新しい数字を追加するためのコンポーネントです。
src/components/AddNumbers.tsx
import { FC, useState } from "react"; import { useSetRecoilState } from "recoil"; import { numbersState } from "../recoil/atoms"; const AddNumbers: FC = () => { const [inputNumber, setInputNumber] = useState(0); const setNumbers = useSetRecoilState(numbersState); const increment = () => { setInputNumber((num) => num + 1); }; const decrement = () => { setInputNumber((num) => num - 1); }; // atom 内の value を変更 const addNumber = () => { setNumbers((oldNumbers) => { return [...oldNumbers, inputNumber]; }); setInputNumber(0); }; return ( <> <button onClick={decrement}>-1</button> {inputNumber}{" "} <button onClick={increment}>+1</button>{" "} <button onClick={addNumber}>追加</button> </> ); }; export default AddNumbers;
useSetRecoilState を使うと atom などの値を書き換えることが出来ます。書き換えたい atom と前の値を受け取って新しい値を返す関数を与えると、書き換えが行われます。
次にどのような数字を表示するかユーザーが決められるセレクトボックスを作ります。
src/components/FilterNumbers.tsx
import { FC } from "react"; import { useRecoilState } from "recoil"; import { numbersFilterState } from "../recoil/atoms"; const FilterNumbers: FC = () => { const [filter, setFilter] = useRecoilState(numbersFilterState); // atom 内の value を変更 const updateFilter = (event: React.ChangeEvent<{ value: unknown }>) => { setFilter(event.target.value as string); }; return ( <> <select value={filter} onChange={updateFilter}> <option value="all">全て</option> <option value="odd">奇数</option> <option value="even">偶数</option> </select> </> ); }; export default FilterNumbers;
useRecoilState を使うと atom の読み書きが出来ます。
最後に数字を表示するコンポーネントを作ります。
src/components/ShowNumbers.tsx
import { FC } from "react"; import { useRecoilState } from "recoil"; import { numbersFilterState } from "../recoil/atoms"; const FilterNumbers: FC = () => { const [filter, setFilter] = useRecoilState(numbersFilterState); // atom 内の value を変更 const updateFilter = (event: React.ChangeEvent<{ value: unknown }>) => { setFilter(event.target.value as string); }; return ( <> <select value={filter} onChange={updateFilter}> <option value="all">全て</option> <option value="odd">奇数</option> <option value="even">偶数</option> </select> </> ); }; export default FilterNumbers;
値だけ欲しい時は useRecoilValue を使います。あとはコンポーネントを並べれば完成です!
src/App.tsx
import { FC } from "react"; import AddNumbers from "./components/AddNumbers"; import FilterNumbers from "./components/FilterNumbers"; import ShowNumbers from "./components/ShowNumbers"; const App: FC = () => { return ( <> <AddNumbers /> <FilterNumbers /> <ShowNumbers /> </> ); }; export default App;
yarn start
で実行し、localhost:3000
を確認してみましょう。
終わりに
体感 Redux よりかなり楽に状態管理が出来た気がします。今(2021/4/9) 0.2.0 なので、これから機能の拡充等楽しみです。