recoilで状態管理を行う
2021/07/23
recoilとは
recoil
は Facebook
から2020年5月に発表に発表された状態管理ライブラリ。
公式ページ
セットアップ
npx create-react-app recoil-react-exsample --template typescript
npm install recoil
ルートのコンポーネントを <RecoilRoot>
でラップする。
import React from 'react';
import { RecoilRoot } from 'recoil';
import './App.css';
function App() {
return (
<RecoilRoot>
...
</RecoilRoot>
);
}
export default App;
atom
ひとまず配列を取得するatomを宣言する。
export interface todo{
id: number,
text: string,
isComplete: boolean,
};
export type todos = todo[];
import { todos } from '../@types/todo';
import { atom } from 'recoil';
export const todoListState = atom<todos>({
key: 'todoListState',
default: [],
});
表示はこのように。
import { todoListState } from '../../atoms/todoLists'
import { useRecoilValue } from 'recoil';
const TodoList = () => {
const todoList = useRecoilValue(todoListState);
return (
<div className="TodoList">
{
todoList.map((v,i) => {
return (
<div key={i}>
<p>{v.id}</p>
<p>{v.text}</p>
<p>{v.isComplete ? "yes":"no"}</p>
</div>
)
})
}
</div>
);
}
export default TodoList;
変更はatomを通して行う。
import { useState, ChangeEvent } from 'react';
import { useSetRecoilState } from 'recoil';
import { todoListState } from '../../atoms/todoLists'
const TodoListCreator = () => {
const [inputValue, setInputValue] = useState('');
const setTodoList = useSetRecoilState(todoListState);
const addItem = () => {
setTodoList((oldTodoList) => [
...oldTodoList,
{
id: getId(),
text: inputValue,
isComplete: id % 2 == 0
},
]);
setInputValue('');
};
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value);
};
return (
<div>
<input type="text" value={inputValue} onChange={onChange} />
<button onClick={addItem}>Add</button>
</div>
);
}
export default TodoListCreator;
let id = 0;
function getId() {
return id++;
}
useRecoilValue
でatomから値を取得する。
変更する時は useSetRecoilState
を使いatomの値を変更する。
selector
atomの値を使い加工したデータを取得する時に使用する。
import { atom, selector } from "recoil";
import { todoListState } from '../atoms/todoLists'
import { todos } from '../@types/todo';
export const todoListFilterState = atom<'Show All' | 'Show Completed' | 'Show Uncompleted'>({
key: 'todoListFilterState',
default: 'Show All',
});
export const filteredTodoListState = selector<todos>({
key: 'filteredTodoListState',
get: ({get}) => {
const filter = get(todoListFilterState);
const list = get(todoListState);
switch (filter) {
case 'Show Completed':
return list.filter((item) => item.isComplete);
case 'Show Uncompleted':
return list.filter((item) => !item.isComplete);
default:
return list;
}
},
});
import React from 'react';
import { todoListFilterState, filteredTodoListState } from '../../selectors/filterTodoList'
import { useRecoilValue, useRecoilState } from 'recoil';
const TodoListFilter = () => {
const todoList = useRecoilValue(filteredTodoListState);
const [filterValue, setFilter] = useRecoilState(todoListFilterState);
setFilter("Show Completed");
return (
<div className="TodoList">
<p>filterValue:{filterValue}</p>
{
todoList.map((v,i) => {
return (
<div key={i}>
<p>{v.id}</p>
<p>{v.text}</p>
<p>{v.isComplete ? "yes":"no"}</p>
</div>
)
})
}
</div>
)
};
export default TodoListFilter;
非同期データ
selector
を使い非同期的に状態を取得することが出来る。
import { selector } from "recoil";
export const waitText = selector<string>({
key: 'waitText',
get: async () => {
await new Promise((resolve, reject) => {
setTimeout(()=>{
resolve("s");
},1000)
});
return "complate";
},
});
これを普通に表示するコンポーネントを作成。
import { waitText } from '../../selectors/wait'
import { useRecoilValue } from 'recoil';
const WaitView = () => {
const text = useRecoilValue(waitText);
return (
<div className="WaitView">
{text}
</div>
)
}
export default WaitView;
React.Suspense
を使うことで取得前状態にすることが出来る。
<React.Suspense fallback={<div>Loading...</div>}>
<WaitView/>
</React.Suspense>
終わりに
react hooks
らしく書けるので非常に良い。
今回は atoms
と selectors
をフォルダ分けしたけど 一つの状態としてまとめるべきな気がする。
どのように運用して行くかのサンプルはいくつか見たいなぁと思う。