- 「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[]>({
});