- 「React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 01 ー atomを使った項目操作」
- 「React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 02 ー selectorによるフィルタリングと集計」
- 「React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 03 ー 構成とロジックを整える」
前回は、Recoilの状態をコンポーネントから直に書き替えないようにしました。運用としては、これでもよいかもしれません。けれど、あえてRecoilの設定のフック(たとえばuseSetRecoilState)を使えば、コンポーネントから状態は変えてしまえます。今回のお題は、Recoilの状態の参照もカスタムフックを介すようにすることです。つまり、Recoilの状態はもはやexportしません。書き上がりのコードは、つぎのサンプル001のCodeSandbox作例で確かめられます。
サンプル001■React + TypeScript: Recoil tutorial example 04
todoListFilterStateの状態をカスタムフックから参照する
atomについては、状態をカスタムフックの戻り値に加えればよいことです。todoListFilterStateは、つぎのように書き替えてください。
// import { atom, useSetRecoilState } from 'recoil';
import { atom, useRecoilState } from 'recoil';
export const useFilter = () => {
// const setFilter = useSetRecoilState(todoListFilterState);
const [filter, setFilter] = useRecoilState(todoListFilterState);
// return { setListFilter };
return { filter, setListFilter };
};
コンポーネント(TodoListFilters)の側は、状態(todoListFilterState)を直にimportすることなく、カスタムフック(useFilter)から取り出します。Recoilの参照のフック(useRecoilValue)も使わずに済むということです。
// import { useRecoilValue } from 'recoil';
import {
// todoListFilterState,
} from '../state/todoListFilterState';
export const TodoListFilters: FC = () => {
// const filter = useRecoilValue(todoListFilterState);
// const { setListFilter } = useFilter();
const { filter, setListFilter } = useFilter();
};
todoListStateの状態をカスタムフックから参照する
TodoリストのデータをもつtodoListStateも同じです。参照(todoList)はカスタムフックから返すように書き替えます。
export const useTodoList = () => {
return {
todoList,
};
};
コンポーネント(TodoItem)は、Recoilの状態(todoListState)を直にimportしません。参照(todoList)をカスタムフック(useTodoList)から得るのです。
// import { useRecoilValue } from 'recoil';
// import { todoListState, useTodoList } from '../state/todoListState';
import { useTodoList } from '../state/todoListState';
export const TodoItem: FC<Props> = ({ item }) => {
// const todoList = useRecoilValue(todoListState);
const {
todoList,
} = useTodoList();
};
todoListStatsStateのselectorは関数selectorFamilyでつくる
selectorは、Recoilの他の状態を参照して、返す値を決めます。前述のatomと同じように、カスタムフックから状態を取り出したいところです。けれど、オプションオブジェクトのgetコールバックからは、カスタムフックが呼び出せません(トップレベルではないのでエラーになります)。あらかじめ参照は外で得て、selectorに渡すかたちを取らなければならないのです。
そのようなとき、関数selectorFamilyを使えば、getコールバックに引数として値が渡せます(「selectorFamily(options)」参照)。todoListStatsStateの書き替えは、つぎのとおりです。
// import { selector } from 'recoil';
import { selectorFamily, useRecoilValue } from 'recoil';
// import { todoListState } from './todoListState';
import { useTodoList } from './todoListState';
import type { TodoItemType } from './todoListState';
// export const todoListStatsState = selector<TodoStatsType>({
export const todoListStatsState = selectorFamily<TodoStatsType, TodoItemType[]>({
// get: ({ get }) => {
get: (todoList) => () => {
// const todoList = get(todoListState);
}
});
export const useTodoListStats = () => {
const { todoList } = useTodoList();
const {
totalNum,
totalCompletedNum,
totalUncompletedNum,
percentCompleted
} = useRecoilValue(todoListStatsState(todoList));
return {
totalNum,
totalCompletedNum,
totalUncompletedNum,
percentCompleted
};
};
そうすると、コンポーネント(TodoListStats)は、Recoilの状態(todoListStatsState)は直にimportしません。カスタムフック(useTodoListStats)から参照が取り出せるからです。useRecoilValueフックも、もはや要りません。
import { FC } from 'react';
// import { useRecoilValue } from 'recoil';
// import { todoListStatsState } from '../state/todoListStatsState';
import { useTodoListStats } from '../state/todoListStatsState';
export const TodoListStats: FC = () => {
const {
// } = useRecoilValue(todoListStatsState);
} = useTodoListStats();
};
filteredTodoListStateのselectorを関数selectorFamilyでつくる
filteredTodoListStateも、selectorは関数selectorFamilyを用いてつくります。状態はfilterとlistのふたつ使いますので、オブジェクトに収めたうえで引数として渡しました。
// import { selector } from 'recoil';
import { selectorFamily, useRecoilValue } from 'recoil';
// import { todoListFilterState } from './todoListFilterState';
import { useFilter } from './todoListFilterState';
import type { FilterType } from './todoListFilterState';
// import { todoListState } from '../state/todoListState';
import { useTodoList } from './todoListState';
type ItemListWithFilter = {
filter: FilterType;
list: TodoItemType[];
};
// export const filteredTodoListState = selector<TodoItemType[]>({
export const filteredTodoListState = selectorFamily<
TodoItemType[],
ItemListWithFilter
>({
// get: ({ get }) => {
get: ({ filter, list }) => () => {
// const filter: FilterType = get(todoListFilterState);
// const list = get(todoListState);
}
});
export const useFilteredTodoList = () => {
const { filter } = useFilter();
const { todoList: list } = useTodoList();
const filteredTodoList = useRecoilValue(
filteredTodoListState({ filter, list })
);
return filteredTodoList;
};
コンポーネント(TodoList)は、状態(filteredTodoListState)は直にimportすることなく、カスタムフック(useFilteredTodoList)から参照(todoList)を得ればよいのです。
// import { useRecoilValue } from "recoil";
// import { filteredTodoListState } from "../state/filteredTodoListState";
import { useFilteredTodoList } from "../state/filteredTodoListState";
export const TodoList: FC = () => {
// const todoList = useRecoilValue(filteredTodoListState);
const todoList = useFilteredTodoList();
};
Recoilの状態のexportを外す
これで、Recoilの状態を直にimportするコンポーネントモジュールはなくなりました。atomとselectorのexportは、すべて外して構わないということです。機械的な書き替えなので、コードは以下に羅列します。Recoilの状態は、もはやカスタムフックを介さないかぎり、外から参照も設定もできません。各モジュールのコードについては、冒頭に掲げたサンプル001のCodeSandbox作例でお確かめください。
// export const filteredTodoListState = selectorFamily<
const filteredTodoListState = selectorFamily<
TodoItemType[],
ItemListWithFilter
>({
});
// export const todoListFilterState = atom<FilterType>({
const todoListFilterState = atom<FilterType>({
});
// export const todoListState = atom<TodoItemType[]>({
const todoListState = atom<TodoItemType[]>({
});
// export const todoListStatsState = selectorFamily<TodoStatsType, TodoItemType[]>(
const todoListStatsState = selectorFamily<TodoStatsType, TodoItemType[]>({
});