前準備
Recoilのインストール
他のライブラリなどと同じくパッケージのインストールを行います。
yarn add recoil
yarn add @types/recoil
npm i recoil
npm i @types/recoil
実装
RecoilRoot
まずはプロジェクトの大本であるApp
を
RecoilRoot
タグで囲みます。このRecoilRoot
タグに囲まれていればRecoilによって管理されている値を参照できるようになります。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { RecoilRoot } from 'recoil';
ReactDOM.render(
<React.StrictMode>
<RecoilRoot>
<App />
</RecoilRoot>
</React.StrictMode>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
Atom
AtomはAtomを識別するためのkey
とデフォルトの値であるdefault
をatomメソッドに渡すことによって作成されます。
import { atom } from 'recoil';
const todoAtomState = atom<string>({
key: "todoAtom",
default: "",
});
export default todoAtomState;
Atomの変更
入力用フォームのコンポーネントを作成します。
状態を更新するためにはuseSetRecoilState
を使用します。
const InputForm = () => {
const setTodo = useSetRecoilState<string[]>(todoAtomState);
return (
<>
<input onChange={(val) => setTodo(val.target.value)}></input>
</>
)
}
export default InputForm;
Atomの取得
次にTodoリストの表示用コンポーネントの実装です。
Atomの状態の取得にはuseRecoilValue
を使用します。
const TodoList = () => {
const todo = useRecoilValue<string[]>(todoAtomState);
return (
<>
<span style={{ display: "block" }}>{todo}</span>
</>
)
}
export default TodoList;
const App = () => {
return (
<div className="App">
<InputForm />
<TodoList />
</div>
);
}
export default App;
おまけ
Hooks APIのuseState
のような記述でもgetとsetを行うことができます。
[state, setState] = useRecoilState(**atom**)
このような感じでInputForm
での更新がTodoList
に反映されているのが確認できました。
しかしこのままではただの値の更新をしただけになってしまうため、リストを作成していきたいと思います。
本編
入力フォーム
↑まではinputのonChange
イベントでAtomの値を更新していましたが、ここをHooks APIのuseState
を用いて一時的に入力された情報を保持するように変更します。
const InputForm = () => {
const [state, setState] = useState<string>("");
return (
<>
<input onChange={(val) => setState(val.target.value)}></input>
</>
)
}
追加ボタンのonClick
イベントでAtomsで管理している配列に値の追加を行います。
useSetRecoilState
は更新前の値を取ることができるので、これを使用して配列の更新を行います。
const InputForm = () => {
const [val, setVal] = useState<string>("");
const setTodo = useSetRecoilState<string[]>(todoAtomState);
return (
<>
<input onChange={(val) => setVal(val.target.value)}></input>
<button onClick={() => setTodo((oldState) => [...oldState, val])}>
Add
</button>
</>
);
};
リスト表示
Todoの配列の長さをもとに簡単なリストを表示するように変更します。
const TodoList = () => {
const todo = useRecoilValue<string[]>(todoAtomState);
return (
<ul>
{todo.map((val) => {
return <li>{val}</li>;
})}
</ul>
);
};
↓がこの時点での動作です。
想定通りリスト表示が機能するようになりました。
達成状況の管理
Todoリストなのでその項目が達成できているのかどうかの管理も必要となります。
項目名のname
と達成状況のfinished
を持つITodo
を定義します。
export interface ITodo {
name: string;
finished: boolean;
}
完了状況を更新するためのstatusChanged
を実装、チェックボックスのonChange
イベントに登録します。
atomで管理している完了状況をもとに、完了した項目は→のようにに打ち消し線で消すようにします。
const TodoList = () => {
const todo: ITodo[] = useRecoilValue<ITodo[]>(todoAtomState);
const setTodo = useSetRecoilState<ITodo[]>(todoAtomState);
const statusChanged = (targetVal: ITodo) => {
const index = todo.findIndex((item: ITodo) => item === targetVal);
const newTodo: ITodo = { ...todo[index], finished: !targetVal.finished };
setTodo([...todo.slice(0, index), newTodo, ...todo.slice(index + 1)]);
};
return (
<ul style={{ listStyle: "none" }}>
{todo.map((val) => {
return (
<li>
<input type="checkbox" onChange={() => statusChanged(val)}></input>
<span
style={
val.finished
? { textDecoration: "line-through" }
: { textDecoration: "none" }
}
>
{val.name}
</span>
</li>
);
})}
</ul>
);
};
最終的にこのような完了状態の管理ができるTodoリストが出来上がりました。
おわり
状態管理の単位が個々で独立しているためシンプルに管理を行うことができそうです。また、状態管理が独立していることで、無駄なコンポーネントの再描画がなるためパフォーマンス面において優れています。
個人的にはReduxと比較しActionやReducerが存在せず、状態を操作できる点は魅力的で、それらがないことで準備段階のハードルも低くReduxよりも使いやすく高評価でした。