メインコンテンツにスキップ

セレクター

セレクターは、派生状態を表します。派生状態は、状態を純粋関数に渡して、その状態から新しい値を導き出すことによって得られる出力と考えることができます。

派生状態は、他のデータに依存する動的なデータを構築できる強力な概念です。ToDoリストアプリケーションのコンテキストでは、以下が派生状態と見なされます。

  • フィルタリングされたToDoリスト:完了した項目をフィルタリングするなど、特定の基準に基づいて一部の項目がフィルタリングされた新しいリストを作成することにより、完全なToDoリストから派生します。
  • ToDoリストの統計:リスト内の項目の総数、完了した項目の数、完了した項目の割合など、リストの有用な属性を計算することにより、完全なToDoリストから派生します。

フィルタリングされたToDoリストを実装するには、アトムに値を保存できるフィルター基準のセットを選択する必要があります。使用するフィルターオプションは、「すべて表示」、「完了を表示」、「未完了を表示」です。デフォルト値は「すべて表示」になります。

const todoListFilterState = atom({
key: 'TodoListFilter',
default: 'Show All',
});

todoListFilterStatetodoListStateを使用して、フィルタリングされたリストを導出するfilteredTodoListStateセレクターを構築できます。

const filteredTodoListState = selector({
key: 'FilteredTodoList',
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;
}
},
});

filteredTodoListStateは、内部的に2つの依存関係(todoListFilterStatetodoListState)を追跡し、いずれかが変更された場合に再実行されるようにします。

コンポーネントの観点から見ると、セレクターは、アトムを読み取るために使用されるのと同じフックを使用して読み取ることができます。ただし、特定のフックは書き込み可能な状態(つまり、useRecoilState())でのみ機能することに注意することが重要です。すべてのアトムは書き込み可能な状態ですが、一部のセレクターのみが書き込み可能な状態と見なされます(getプロパティとsetプロパティの両方を持つセレクター)。このトピックの詳細については、コアコンセプトページを参照してください。

フィルタリングされたToDoリストを表示することは、TodoListコンポーネントで1行を変更するのと同じくらい簡単です。

function TodoList() {
// changed from todoListState to filteredTodoListState
const todoList = useRecoilValue(filteredTodoListState);

return (
<>
<TodoListStats />
<TodoListFilters />
<TodoItemCreator />

{todoList.map((todoItem) => (
<TodoItem item={todoItem} key={todoItem.id} />
))}
</>
);
}

todoListFilterStateのデフォルト値が"Show All"であるため、UIはすべてのToDoを表示していることに注意してください。フィルターを変更するには、TodoListFiltersコンポーネントを実装する必要があります。

function TodoListFilters() {
const [filter, setFilter] = useRecoilState(todoListFilterState);

const updateFilter = ({target: {value}}) => {
setFilter(value);
};

return (
<>
Filter:
<select value={filter} onChange={updateFilter}>
<option value="Show All">All</option>
<option value="Show Completed">Completed</option>
<option value="Show Uncompleted">Uncompleted</option>
</select>
</>
);
}

数行のコードで、フィルタリングを実装することができました!同じ概念を使用して、TodoListStatsコンポーネントを実装します。

次の統計を表示します。

  • ToDo項目の総数
  • 完了した項目の総数
  • 未完了の項目の総数
  • 完了した項目の割合

各統計にセレクターを作成することもできますが、必要なデータを含むオブジェクトを返す1つのセレクターを作成する方が簡単なアプローチです。このセレクターをtodoListStatsStateと呼びます。

const todoListStatsState = selector({
key: 'TodoListStats',
get: ({get}) => {
const todoList = get(todoListState);
const totalNum = todoList.length;
const totalCompletedNum = todoList.filter((item) => item.isComplete).length;
const totalUncompletedNum = totalNum - totalCompletedNum;
const percentCompleted = totalNum === 0 ? 0 : totalCompletedNum / totalNum * 100;

return {
totalNum,
totalCompletedNum,
totalUncompletedNum,
percentCompleted,
};
},
});

todoListStatsStateの値を読み取るには、再度useRecoilValue()を使用します。

function TodoListStats() {
const {
totalNum,
totalCompletedNum,
totalUncompletedNum,
percentCompleted,
} = useRecoilValue(todoListStatsState);

const formattedPercentCompleted = Math.round(percentCompleted);

return (
<ul>
<li>Total items: {totalNum}</li>
<li>Items completed: {totalCompletedNum}</li>
<li>Items not completed: {totalUncompletedNum}</li>
<li>Percent completed: {formattedPercentCompleted}</li>
</ul>
);
}

要約すると、すべての要件を満たすToDoリストアプリを作成しました。

  • ToDo項目を追加する
  • ToDo項目を編集する
  • ToDo項目を削除する
  • ToDo項目をフィルターする
  • 役立つ統計を表示する