React + TypeScript: 通过 Recoil 公式教程创建 Todo 列表 01 – 使用 atom 进行项目操作
-
- 「React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 02 ー selectorによるフィルタリングと集計」
-
- 「React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 03 ー 構成とロジックを整える」
- 「React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 04 ー 状態を直接exportしないようにする」
Recoil是由Meta(Facebook的子公司)开发的用于管理React状态的库。它不是将状态整合到一起,而是细分并单独处理每一个状态。在文章《使用React + TypeScript:尝试使用Facebook的状态管理库Recoil》中,我们通过简单的代码示例解释了它的大致原理。
本系列的主题是基于Recoil官方的”Basic Tutorial”中的示例,一个名为Todo List的任务清单。不过,我们对代码进行了模块分离,并加入了通过TypeScript进行类型定义的功能。本文首次介绍的是使用atom来进行列表项的添加、编辑和删除操作(示例001)。
样本001■React + TypeScript:Recoil教程示例01
确定根组件
使用React应用程序的模板,可以使用Create React App创建。您可以阅读”使用Create React App创建带有TypeScript的模板应用程序”来了解如何创建。
想要共享状态的组件树根节点必须包裹在中(参考”用包裹组件树”)。通过这样做,所有子孙组件都可以使用相同的状态。父组件将在稍后定义为TodoList(代码003)。
代码001■待办事项清单应用程序组件。
import { RecoilRoot } from 'recoil';
import { TodoList } from './TodoList';
export default function App() {
return (
<RecoilRoot>
<TodoList />
</RecoilRoot>
);
}
用atom来确定Todo清单的状态
atom是一个可信的源(source of truth),用于表示应用程序的状态 (参见 “可信的唯一信息源”)。在todoListState中,使用函数atom()来定义Todo项(TodoItemType类型)的列表(数组)数据(状态)。给定的参数选项对象中的key是状态的唯一标识符 (todoListState),default是状态的初始值 (TodoItemType[]类型的空数组)。
用以下的代码002■来确定待办事项列表的数据
import { atom } from 'recoil';
export type TodoItemType = {
id: number;
text: string;
isComplete: boolean;
};
export const todoListState = atom<TodoItemType[]>({
key: 'todoListState',
default: [],
});
使用 useRecoilValue 钩子 来读取状态
useRecoilValue()是一个从参数atom中读取值的钩子。与前文中使用的useRecoilState()不同,它不返回设置函数(也就是说只能读取值,无法进行设置)。
TodoList组件将从提取的Todo列表数组(todoList)中按顺序提供元素(todoItem)的数据给定项的组件TodoItem来进行列表显示。
代码003■Todo列表的父组件
import type { FC } from 'react';
import { useRecoilValue } from 'recoil';
import { TodoItem } from './TodoItem';
import { TodoItemCreator } from './TodoItemCreator';
import { todoListState } from './todoListState';
export const TodoList: FC = () => {
const todoList = useRecoilValue(todoListState);
return (
<>
<TodoItemCreator />
{todoList.map((todoItem) => (
<TodoItem key={todoItem.id} item={todoItem} />
))}
</>
);
};
使用 useSetRecoilState 钩子来设置状态值。
TodoItemCreator是一个组件,它将作为属性传递给使用useSetRecoilState()钩子函数来设置状态的atom的状态,并将元素中输入的文本作为Todo项目添加到todoListState状态中(Code 004)。返回值setTodoList是状态设置函数。
调用状态设置函数setTodoList使用了函数式的更新方法,传入的参数是一个函数,该函数接收旧的Todo列表oldTodoList作为参数,并返回新的值。新的Todo项将被添加到当前的Todo列表todoListState中。
将待办事项列表项添加到数据中的组件:Code 004。
import { useCallback, useState } from 'react';
import type { ChangeEventHandler, FC } from 'react';
import { useSetRecoilState } from 'recoil';
import { todoListState } from './todoListState';
// utility for creating unique Id
let id = 0;
const getId = () => {
return id++;
}
export const TodoItemCreator: FC = () => {
const [inputValue, setInputValue] = useState('');
const setTodoList = useSetRecoilState(todoListState);
const addItem = useCallback(() => {
setTodoList((oldTodoList) => [
...oldTodoList,
{
id: getId(),
text: inputValue,
isComplete: false,
},
]);
setInputValue('');
}, [inputValue, setTodoList]);
const onChange: ChangeEventHandler<HTMLInputElement> = useCallback(
({ target: { value } }) => {
setInputValue(value);
},
[]
);
return (
<div>
<input type="text" value={inputValue} onChange={onChange} />
<button onClick={addItem}>Add</button>
</div>
);
};
使用 useRecoilState 钩子来读取和写入状态的值
TodoItem组件用于显示从Todo列表状态(atom)数据中提取的每个项目。它包含一个复选框来表示是否已完成的状态,并且还有一个删除按钮来删除该项目。它还可以编辑(添加)项目文本。换句话说,必须能够读写状态的值。
用Recoil提供的钩子函数useRecoilState()可以读写atom的状态。其语法与useState相同,返回值数组的第一个元素(todoList)是状态变量,第二个元素(setTodoList)是设置函数。不同之处在于,Recoil的状态是在组件树中共享的。
Todo项目的数据由组件以参数对象(item)的形式接收,并定义了编辑(editItemText)、勾选(toggleItemCompletion)和删除(deleteItem)的函数,代码如下(参考代码005)。实际的操作效果,请查看之前发布在CodeSandbox上的示例001来确认。
用于显示、检查、编辑和删除代码005项目的组件
import { useCallback } from 'react';
import type { ChangeEventHandler, FC } from 'react';
import { useRecoilState } from 'recoil';
import { todoListState } from './todoListState';
import type { TodoItemType } from './todoListState';
type Props = {
item: TodoItemType;
};
const replaceItemAtIndex = (
arr: TodoItemType[],
index: number,
newValue: TodoItemType
) => {
return [...arr.slice(0, index), newValue, ...arr.slice(index + 1)];
};
const removeItemAtIndex = (arr: TodoItemType[], index: number) => {
return [...arr.slice(0, index), ...arr.slice(index + 1)];
};
export const TodoItem: FC<Props> = ({ item }) => {
const [todoList, setTodoList] = useRecoilState(todoListState);
const index = todoList.findIndex((listItem) => listItem === item);
const editItemText: ChangeEventHandler<HTMLInputElement> = useCallback(
({ target: { value } }) => {
const newList = replaceItemAtIndex(todoList, index, {
...item,
text: value,
});
setTodoList(newList);
},
[index, item, setTodoList, todoList]
);
const toggleItemCompletion = useCallback(() => {
const newList = replaceItemAtIndex(todoList, index, {
...item,
isComplete: !item.isComplete,
});
setTodoList(newList);
}, [index, item, setTodoList, todoList]);
const deleteItem = useCallback(() => {
const newList = removeItemAtIndex(todoList, index);
setTodoList(newList);
}, [index, setTodoList, todoList]);
return (
<div>
<input type="text" value={item.text} onChange={editItemText} />
<input
type="checkbox"
checked={item.isComplete}
onChange={toggleItemCompletion}
/>
<button onClick={deleteItem}>X</button>
</div>
);
};
-
- 「React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 02 ー selectorによるフィルタリングと集計」
-
- 「React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 03 ー 構成とロジックを整える」
- 「React + TypeScript: Recoil公式チュートリアルのTodoリストをつくる 04 ー 状態を直接exportしないようにする」