4
2

React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 04 ー 状態を直接exportしないようにする

Last updated at Posted at 2022-02-22

前回は、Recoilの状態をコンポーネントから直に書き替えないようにしました。運用としては、これでもよいかもしれません。けれど、あえてRecoilの設定のフック(たとえばuseSetRecoilState)を使えば、コンポーネントから状態は変えてしまえます。今回のお題は、Recoilの状態の参照もカスタムフックを介すようにすることです。つまり、Recoilの状態はもはやexportしません。書き上がりのコードは、つぎのサンプル001のCodeSandbox作例で確かめられます。

サンプル001■React + TypeScript: Recoil tutorial example 04

todoListFilterStateの状態をカスタムフックから参照する

atomについては、状態をカスタムフックの戻り値に加えればよいことです。todoListFilterStateは、つぎのように書き替えてください。

src/state/todoListFilterState.ts
// 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)も使わずに済むということです。

src/components/TodoListFilters.tsx
// 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)はカスタムフックから返すように書き替えます。

src/state/todoListState.ts
export const useTodoList = () => {

	return {

		todoList,

	};
};

コンポーネント(TodoItem)は、Recoilの状態(todoListState)を直にimportしません。参照(todoList)をカスタムフック(useTodoList)から得るのです。

src/components/TodoItem.tsx
// 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();

};

todoListStatsStateselectorは関数selectorFamilyでつくる

selectorは、Recoilの他の状態を参照して、返す値を決めます。前述のatomと同じように、カスタムフックから状態を取り出したいところです。けれど、オプションオブジェクトのgetコールバックからは、カスタムフックが呼び出せません(トップレベルではないのでエラーになります)。あらかじめ参照は外で得て、selectorに渡すかたちを取らなければならないのです。

そのようなとき、関数selectorFamilyを使えば、getコールバックに引数として値が渡せます(「selectorFamily(options)」参照)。todoListStatsStateの書き替えは、つぎのとおりです。

src/state/todoListStatsState.ts
// 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フックも、もはや要りません。

src/components/TodoListStats.tsx
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();

};

filteredTodoListStateselectorを関数selectorFamilyでつくる

filteredTodoListStateも、selectorは関数selectorFamilyを用いてつくります。状態はfilterlistのふたつ使いますので、オブジェクトに収めたうえで引数として渡しました。

src/state/filteredTodoListState.ts
// 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)を得ればよいのです。

src/components/TodoList.tsx
// 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するコンポーネントモジュールはなくなりました。atomselectorexportは、すべて外して構わないということです。機械的な書き替えなので、コードは以下に羅列します。Recoilの状態は、もはやカスタムフックを介さないかぎり、外から参照も設定もできません。各モジュールのコードについては、冒頭に掲げたサンプル001のCodeSandbox作例でお確かめください。

src/state/filteredTodoListState.ts
// export const filteredTodoListState = selectorFamily<
const filteredTodoListState = selectorFamily<
	TodoItemType[],
	ItemListWithFilter
>({

});
src/state/todoListFilterState.ts
// export const todoListFilterState = atom<FilterType>({
const todoListFilterState = atom<FilterType>({

});
src/state/todoListState.ts
// export const todoListState = atom<TodoItemType[]>({
const todoListState = atom<TodoItemType[]>({

});
src/state/todoListStatsState.ts
// export const todoListStatsState = selectorFamily<TodoStatsType, TodoItemType[]>(
const todoListStatsState = selectorFamily<TodoStatsType, TodoItemType[]>({

});
4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2