概要-Recoilとは
Reactにおける状態管理ライブラリの一つです。
状態管理とは
本来のReactの状態管理では、親のコンポーネントから子のコンポーネントへpropsという形で情報を渡しますが、複雑なアプリケーションになるとバケツリレーのような形になってしまい、管理が大変になってしまいます。
状態管理のライブラリを使用することで、Store(ストア)に保存しておけば、直接コンポーネントから参照することができます。
どのような時に使われるか
例えば
- ユーザーがログインしているかしていないか
- 投稿内容のタイトルや内容
RecoilとReduxの違い
Redux 大きく一つのStoreがある
Recoil 複数のStoreがある
Recoilの一つ一つのストアは 「atom(アトム)」 と呼ばれる最小単位です。
複数のatomで状態を管理し、必要なコンポーネントで呼び出して使用することができます。
Reduxの一つのStoreで管理するデメリット・Recoilのメリット
Reduxでは1つのStoreで管理をしているため、もしStoreに空のオブジェクトが更新された時、Reduxを使用しているすべてのコンポーネントが空のオブジェクトに上書きされてしまいます。上手く表示されないバグの原因を生み出す可能性があります。
一方でRecoilでは複数の最小単位のバスターを使っているため、全体を更新されるReduxのデメリットを抑えることができます。
また、atom自体に保管されている値を更新するためには 「Selecter(セレクター)」 という関数を使用します。
RecoilとuseContextの違い
useContextはReactの状態管理のhooksです。
useContextは、根本となるコンポーネントで 「Provider(プロバイダー)」 を宣言し、コンポーネント配下のコンポーネントでもグローバルな状態変数を扱えるようになります。
useContextだと一番下の枝の部分を更新したい時に、一番根本となるコンポーネントを更新しないといけません。
useContextのデメリットは、祖先のコンポーネントも更新する必要があるため、中間にあるコンポーネントも全部レンダリングされてしまい、不要な再レンダリングが発生してしまうことです。
Recoilを使用することで、該当のコンポーネントだけに影響するため、useContextのように祖先から更新される心配はありません。
ですが、Recoilは複数のatomを使用することから複数人で開発する際には、意図しない状態変更が行われる可能性もあります。
「複数人でRecoilを使うときに意図しない状態変更が行われる可能性」
色々なコンポーネントから利用され、状態を更新されるため、Aさんが担当したコンポーネントで更新した状態はCさんが担当したコンポーネントでは意図した状態でない可能性があります。
パフォーマンスの観点や、運用のしやすさからどのライブラリを使用するかの選定をする必要があります。
RecoilでTODOアプリを作る
ReactとTypecript、Recoilを使って簡単なTODOアプリを作ります。
開発環境設定、Recoilのインストール
今回はviteを使用します。
$ npm create vite
Project nameを設定し、React・Typescriptを選択し、ディレクトリを作成します。
ディレクトリを開いて、下記を実行し、インストールします。
$ npm i
ローカルサーバーが5173番で開きます。
$ npm run dev
Recoilをインストールします。
$ npm i recoil
Typescriptも使用できるようにインストールしておきます。
$ npm i @types/recoil
RecoilRootの設定
Recoilを使用するためにコンポーネントをRecoilRoot
で囲みます。
import "./App.css";
import AddTask from "./components/AddTask";
import InputTask from "./components/InputTask";
import { RecoilRoot } from "recoil";
function App() {
return (
<RecoilRoot>
<div className="App">
<InputTask />
<AddTask />
</div>
</RecoilRoot>
);
}
export default App;
コンポーネントの作成
src/components
フォルダを追加し、今回のアプリに必要なInputTask.tsx
、AddTask.tsx
を作成します。
useRecoilState()
stateの取得・更新を行う場合、useRecoilState()を使用します。
useRecoilState()の引数には使用したいstateを渡します。
useRecoilValue()
stateの値だけを取得する場合、useRecoilValue()を使用します。
useRecoilValue()は引数に使用するstateのkeyを渡します。
import React, { useCallback } from "react";
import "./InputTask.css";
import { inputTitleState } from "../states/inputTitleState";
import { useRecoilState } from "recoil";
import { addTitleState } from "../states/addTitleState";
// idの値をランダムに設定するための関数
const getKey = () => Math.random().toString(32).substring(2);
const InputTask = () => {
const [inputTitle, setInputTitle] = useRecoilState(inputTitleState);
const [addTitle, setAddTitle] = useRecoilState(addTitleState);
const onChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setInputTitle(e.target.value);
},
[inputTitle]
);
const handleClick = () => {
setAddTitle([...addTitle, { id: getKey(), title: inputTitle }]);
setInputTitle("");
};
return (
<div className="inputField">
<input type="text" className="inputTitle" onChange={onChange} value={inputTitle}/>
<button type="button" className="addButton" onClick={handleClick}>
追加
</button>
</div>
);
};
export default InputTask;
import React from "react";
import { useRecoilValue } from "recoil";
import { addTitleState, addTitleStateLength } from "../states/addTitleState";
const AddTask = () => {
// useRecoilValue:値だけを取ってくる
const addTitle = useRecoilValue(addTitleState);
const addTitleLength = useRecoilValue(addTitleStateLength);
return (
<div className="taskField">
<div>{addTitleLength}個のタスク</div>
<ul>
{addTitle.map((task) => (
<li key={task.id}>{task.title}</li>
))}
</ul>
</div>
);
};
export default AddTask;
Atomの設定
AtomはStateを管理するための最小単位です。
今回はsrc/states
フォルダを作成し、その中にAtomの設定を行います。
Atomは状態の一部を表します。Atomは、任意のコンポーネントから読み書きすることができます。Atomの値を読み取るコンポーネントは暗黙的にそのAtomにサブスクライブされるため、Atomが更新されると、そのアトムにサブスクライブされているすべてのコンポーネントが再レンダリングされます。
Recoil公式ドキュメントより引用
import { atom } from "recoil";
export const inputTitleState = atom<string>({
key: "inputTitleState",
default: "",
});
Selecter
atomの中になんらかの操作を行いたい(非同期処理・配列の長さの指定など)ときはSelecterを使います。
セレクターは、派生状態の一部を表します
Recoil公式ドキュメントより引用
今回はaddTitleStateの配列の長さを取得するためにSelecterを使用しました。
import { atom, selector } from "recoil";
import { Task } from "../types/Task";
export const addTitleState = atom<Array<Task>>({
key: "addTitleState",
default: [],
});
// atomの中になんらかの操作を行いたい(非同期処理・配列の長さの指定など)ときはSelecterを使う
export const addTitleStateLength = selector<number>({
key: "addTitleStateLength",
// 長さを取得
get: ({ get }) => {
const addTitleNumber: Array<Task> = get(addTitleState);
return addTitleNumber?.length || 0;
},
});
TypeScriptの型ファイルの設定
export type Task = {
id: string;
title:string;
}
まとめ
Reactの状態管理はさまざまなライブラリや手法が存在しているため、設計時の選定が難しくなっています。
Google Trends で比較をしてみると、Recoilが徐々に伸びてきている傾向があります。
状態管理のキャッチアップはしっかり行っていく必要があることを学びました。
参考
Recoil公式ドキュメント