Recoil を隠蔽して使いたかったので、その個人的な備忘録です。
やりたいこと
- Recoil をカプセル化して、Flux patternのように Action / Getter を作りたい
- 全レイヤーが Recoil を直接 import しないようにしたい
- サンプルとして jsonplaceholder から fetch
環境
- React: 17.0.2
- Next: 11.1.3
- Recoil: ^0.7.3-alpha.2
サンプルコード
src/store/todoList/types.ts
src/store/todoList/types.ts
// 本来は domain層で定義
export type Todo = {
id: number;
title: string;
userId: number;
completed: false;
};
/** State の型 */
export type TodoListState = {
todoList: Todo[];
};
/** Getters の型 */
export type TodoListGetters = {
useTodoList: () => TodoListState;
};
/** Actions の型 */
export type TodoListActions = {
useFetchTodoList: () => {
fetchTodoList: () => void;
};
useSetTodo: () => {
setTodo: (todo: Todo) => void;
};
};
src/store/todoList/main.ts
src/store/todoList/main.ts
import { useCallback } from 'react';
import { atom, useRecoilValue, useSetRecoilState } from 'recoil';
import recoilUseCase from '@/application/recoilTest';
import { Todo, TodoListActions, TodoListGetters, TodoListState } from './types';
import { RECOIL_ATOMS_KEYS } from '../keys';
// state はそのまま export しない
const todoListState = atom<TodoListState>({
key: RECOIL_ATOMS_KEYS.TODO_LIST_STATE,
default: {
todoList: [],
},
});
// Getter的役割
const useTodoList = () => {
return useRecoilValue(todoListState);
};
export const todoListGetters: TodoListGetters = {
useTodoList,
};
// Action をカスタムフックとして定義
/** todoList の fetch */
const useFetchTodoList = () => {
const setState = useSetRecoilState(todoListState);
const { data } = recoilUseCase.fetch();
const fetchTodoList = useCallback(() => {
setState(() => {
if (!data) {
return {
todoList: [],
};
}
return {
todoList: data,
};
});
}, [data, setState]);
// TodoListState の保持している state が複数ある場合
// const fetchTodoList = useCallback(() => {
// setState((prev) => {
// if (!data) {
// return {
// todoList: [],
// };
// }
// return {
// ...prev,
// todoList: data,
// };
// });
// }, [data, setState]);
return { fetchTodoList };
};
/** Todoを追加 */
const useSetTodo = () => {
const setState = useSetRecoilState(todoListState);
// 引数がある場合は、useCallbackの第一引数へ記述
const setTodo = useCallback(
(todo: Todo) => {
setState((prev) => {
return {
todoList: [...prev.todoList, todo],
};
});
},
[setState],
);
return { setTodo };
};
export const todoListActions: TodoListActions = {
useFetchTodoList,
useSetTodo,
};
src/pages/recoil/index.tsx
src/pages/recoil/index.tsx
import { todoListActions } from '@/store/testTodoList';
import RecoilTestTemplate from '@/components/templates/recoilTest';
const RecoilPage = () => {
// pages フォルダで fetch だけする
const { fetchTodoList } = todoListActions.useFetchTodoList();
fetchTodoList();
return <RecoilTestTemplate />;
};
export default RecoilPage;
src/components/recoilTest.tsx
src/components/recoilTest.tsx
import { todoListActions, todoListGetters } from '@/store/testTodoList';
// 本来は domain層
import { Todo } from '@/store/testTodoList/types';
export const RecoilTestTemplate = () => {
// Action
const { setTodo } = todoListActions.useSetTodo();
// Getter
const todoListState = todoListGetters.useTodoList();
const [inputData, setInputData] = useState<string>();
const handleChange = (e: string) => {
setInputData(e);
};
const handleClickButton = () => {
const newTodo: Todo = {
userId: 500,
id: 500,
title: inputData ? inputData : '',
completed: false,
};
setTodo(newTodo);
};
return (
<>
{todoListState.todoList &&
todoListState.todoList.map((item: any) => <p key={item.id}>{item.title}</p>)}
<div>
<input type="text" onChange={(e) => handleChange(e.target.value)} />
<button onClick={handleClickButton}>add</button>
</div>
</>
);
};