跳至主要内容

选择器

一个 **选择器** 代表一个 **派生状态** 的部分。您可以将派生状态视为将状态传递给纯函数的输出,该函数从所述状态派生出一个新值。

派生状态是一个强大的概念,因为它让我们可以构建依赖于其他数据的动态数据。在我们待办事项列表应用程序的上下文中,以下被视为派生状态

  • **过滤后的待办事项列表**:通过创建新的列表派生自完整的待办事项列表,该列表根据某些条件(例如过滤掉已完成的项目)过滤掉了某些项目。
  • **待办事项列表统计信息**:通过计算列表的有用属性(例如列表中的项目总数、已完成项目的数量以及已完成项目的百分比)从完整的待办事项列表派生出来。

为了实现过滤后的待办事项列表,我们需要选择一组其值可以保存在原子中的筛选条件。我们将使用的过滤选项是:“显示全部”、“显示已完成”和“显示未完成”。默认值为“显示全部”

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 在内部跟踪两个依赖项:todoListFilterStatetodoListState,以便在其中任何一个发生更改时重新运行。

从组件的角度来看,选择器可以使用与用于读取原子的相同钩子来读取。但是,重要的是要注意某些钩子仅适用于 **可写状态**(即 useRecoilState())。所有原子都是可写状态,但只有一部分选择器被认为是可写状态(具有 getset 属性的选择器)。有关此主题的更多信息,请参见 核心概念 页面。

显示我们过滤后的待办事项列表就像在 TodoList 组件中更改一行代码一样简单

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

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

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

注意,UI 显示了每个待办事项,因为 todoListFilterState 被赋予了 "Show All" 的默认值。为了更改过滤器,我们需要实现 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 组件。

我们希望显示以下统计信息

  • 待办事项总数
  • 已完成的待办事项总数
  • 未完成的待办事项总数
  • 已完成的待办事项百分比

虽然我们可以为每个统计信息创建一个选择器,但更简单的方法是创建一个返回包含我们需要的数据的对象的选择器。我们将这个选择器称为 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>
);
}

总之,我们已经创建了一个满足我们所有需求的待办事项列表应用程序

  • 添加待办事项
  • 编辑待办事项
  • 删除待办事项
  • 过滤待办事项
  • 显示有用的统计信息