用React + TypeScript创建Recoil官方教程中的Todo列表 02 ー 通过选择器进行过滤和聚合的功能
-
- 「React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 01 ー atomを使った項目操作」
-
- 「React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 03 ー 構成とロジックを整える」
- 「React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 04 ー 状態を直接exportしないようにする」
「React + TypeScript: Recoil公式チュートリアルのTodoリストを作る」第2回では、セレクタを使用します。セレクタは、複数のアトムから参照される値を変更したり返したりするためのものです。他のセレクタからも値を取得することができます。これは純粋な関数であり、派生した状態を作り出します。完成したコードは、サンプル001としてCodeSandboxで公開しました。
React + TypeScript: Recoil教程示例02
筛选项目
首先,我们需要添加的是Todo项目的筛选功能。我们希望能够切换全件/已处理/未处理的三种列表显示。为此,我们引入了新的代码001中的原子变量(todoListFilterState)。
具有所选过滤器作为状态的原子
import { atom } from 'recoil';
export const todoListFilterState = atom<string>({
key: 'todoListFilterState',
default: 'Show All',
});
在以下的代码002中,传递给selector()的参数选项对象的属性get中定义的回调函数,根据状态的值返回处理的结果。用于从回调函数的参数中提取get()来引用值的函数。还请在选项对象中像atom一样提供一个唯一的标识符key属性。
根据筛选器(filteredTodoListState ),selector 通过使用 Array.prototype.filter() 方法从 Todo 列表数组数据(todoListState) 中提取并返回应该显示的项目,依赖于筛选器(atom)的三个值(todoListFilterState)(默认情况下显示所有项目)。
代码002■过滤待办清单的项目选择器
import { selector } from 'recoil';
import { todoListFilterState } from './todoListFilterState';
import { todoListState } from '../state/todoListState';
import type { TodoItemType } from '../state/todoListState';
export const filteredTodoListState = selector<TodoItemType[]>({
key: 'filteredTodoListState',
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;
}
},
});
好啦,父组件TodoList要展示的不再是全部的数据(todoListState),而是被筛选过的项目列表(filteredTodoListState)。因此,请将加载状态替换为下面这样。我在返回值的JSX中添加了一个组件(TodoListFilters),用于切换显示项目。
// import { todoListState } from './todoListState';
import { filteredTodoListState } from './filteredTodoListState';
import { TodoListFilters } from './TodoListFilters';
export const TodoList: FC = () => {
// const todoList = useRecoilValue(todoListState);
const todoList = useRecoilValue(filteredTodoListState);
return (
<>
<TodoListFilters />
</>
);
};
表示切り替え的TodoListFilters是一个包含三个下拉选项的组件(代码003)。可以通过选择菜单中的值来读取和写入atom(todoListFilterState)的值。可以使用useRecoilState()钩子来读取和设置状态。返回值的数组的第一个元素是状态值的引用,可以使用第二个元素的函数来进行设置。
切换下拉菜单组件用于更改代码003的过滤器。
import { useCallback } from 'react';
import type { ChangeEventHandler, FC } from 'react';
import { useRecoilState } from 'recoil';
import { todoListFilterState } from './todoListFilterState';
export const TodoListFilters: FC = () => {
const [filter, setFilter] = useRecoilState(todoListFilterState);
const updateFilter: ChangeEventHandler<HTMLSelectElement> = useCallback(
({ target: { value } }) => {
setFilter(value);
},
[setFilter]
);
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>
</>
);
};
现在您可以通过下拉菜单切换显示项目为全部/已处理/未处理的列表。
前文中的`selector(filteredTodoListState)`依赖于两个原子(`todoListFilterState`和`todoListState`)的状态,并引用它们的值。Recoil是一个机制,当依赖的值发生变化时,会重新执行该选择器。
将项目的情况数值化展示
接下来,让我们对Todo列表项的状态进行量化表示。我们要求得的是总数/已处理/未处理各自的数量,以及处理进度的比例(百分比)。我们将在下面的代码004中定义集计的选择器。要参考的原子是todoListState一个。从得到的Todo列表数组(todoList)中计算并返回所需的值。与原子基本上只有一个状态值相对应,选择器可以利用多个状态来计算出多个值。
用代码004■将待办事项列表的状态数值化并返回选择器。
import { selector } from 'recoil';
import { todoListState } from './todoListState';
type TodoStatsType = {
totalNum: number;
totalCompletedNum: number;
totalUncompletedNum: number;
percentCompleted: number;
};
export const todoListStatsState = selector<TodoStatsType>({
key: 'todoListStatsState',
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,
};
},
});
下面的代码005中的TodoListStats组件将统计结果显示为一个列表。使用useRecoilValue钩子函数从selector(todoListStatsState)中获取返回值。
编码005■以列表形式展示聚合结果的组件
import type { FC } from 'react';
import { useRecoilValue } from 'recoil';
import { todoListStatsState } from './todoListStatsState';
export const TodoListStats: FC = () => {
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列表中,就可以拥有与Recoil官方教程示例相同的功能。不过,与官方代码不同的是,我们对其进行了模块化,并引入了TypeScript的类型定义(Code 006)。请在开头提到的CodeSandbox示例001“React + TypeScript: Recoil tutorial example 02”中验证其功能。
添加了筛选和项目汇总功能的Todo列表组件
import { FC } from 'react';
import { useRecoilValue } from 'recoil';
import { filteredTodoListState } from './filteredTodoListState';
import { TodoItem } from './TodoItem';
import { TodoItemCreator } from './TodoItemCreator';
import { TodoListFilters } from './TodoListFilters';
import { TodoListStats } from './TodoListStats';
export const TodoList: FC = () => {
const todoList = useRecoilValue(filteredTodoListState);
return (
<>
<TodoListStats />
<TodoListFilters />
<TodoItemCreator />
{todoList.map((todoItem) => (
<TodoItem key={todoItem.id} item={todoItem} />
))}
</>
);
};
-
- 「React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 01 ー atomを使った項目操作」
-
- 「React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 03 ー 構成とロジックを整える」
- 「React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 04 ー 状態を直接exportしないようにする」