使用 React 和 TypeScript 实现待办事项列表
在这篇笔记中,我将详细说明如何用 React 和 TypeScript 实现一个简单的待办事项(To-Do List)应用。这个应用具备了基本功能:添加、编辑和删除待办事项。
项目准备
首先,我需要确保自己已经有一个使用 React 和 TypeScript 的开发环境。
接着,我安装了一些必要的依赖,例如图标库 lucide-react 和一些自定义的 UI 组件(如 Button, Input, Card, CardContent):
npm install lucide-react
我假设我已经设置好了基本的 UI 组件,现在就可以开始实现待办事项列表了。
第一步:定义类型(types/todo.ts)
为了确保代码的类型安全,我先定义了一个 Todo 接口。这个接口描述了每个待办事项的结构,这样我可以在整个项目中复用它,并且 TypeScript 会帮助我确保类型一致性。
在 src/types/todo.ts 文件中,我写下了如下内容:
export interface Todo {
id: number;
text: string;
}
第二步:初始化 TodoList 组件
接下来,我创建了 TodoList 组件。在这个组件中,我使用 useState 钩子来管理待办事项列表、当前输入的待办事项文本、以及正在编辑的待办事项 ID。
在 src/components/TodoList.tsx 中,我实现了这个组件:
import React, { useState } from 'react';
import { Plus, Trash2, Edit2, Check, X } from 'lucide-react';
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Card, CardContent } from "@/components/ui/card";
import { Todo } from '../types/todo';
const TodoList: React.FC = () => {
// 管理所有的待办事项
const [todos, setTodos] = useState<Todo[]>([]);
// 当前输入的待办事项文本
const [newTodo, setNewTodo] = useState<string>('');
// 正在编辑的待办事项 ID
const [editingId, setEditingId] = useState<number | null>(null);
// 当前编辑中的文本
const [editText, setEditText] = useState<string>('');
// 添加新的待办事项
const addTodo = () => {
if (newTodo.trim() !== '') {
setTodos([...todos, { id: Date.now(), text: newTodo }]);
setNewTodo('');
}
};
// 开始编辑某个待办事项
const startEditing = (id: number, text: string) => {
setEditingId(id);
setEditText(text);
};
// 保存编辑的待办事项
const saveEdit = (id: number) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, text: editText } : todo
));
setEditingId(null);
};
// 取消编辑
const cancelEdit = () => {
setEditingId(null);
setEditText('');
};
// 删除待办事项
const deleteTodo = (id: number) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<div className="container mx-auto p-4 max-w-md">
<h1 className="text-2xl font-bold mb-4">待办事项列表</h1>
<div className="flex space-x-2 mb-4">
<Input
type="text"
value={newTodo}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setNewTodo(e.target.value)}
placeholder="添加新的待办事项"
className="flex-grow"
/>
<Button onClick={addTodo}>
<Plus className="w-4 h-4 mr-2" />
添加
</Button>
</div>
<Card>
<CardContent className="p-0">
{todos.map(todo => (
<div key={todo.id} className="flex items-center justify-between p-4 border-b last:border-b-0">
{editingId === todo.id ? (
<div className="flex items-center space-x-2 flex-grow">
<Input
type="text"
value={editText}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setEditText(e.target.value)}
className="flex-grow"
/>
<Button size="icon" onClick={() => saveEdit(todo.id)}>
<Check className="w-4 h-4" />
</Button>
<Button size="icon" variant="outline" onClick={cancelEdit}>
<X className="w-4 h-4" />
</Button>
</div>
) : (
<>
<span className="flex-grow">{todo.text}</span>
<div className="flex space-x-2">
<Button size="icon" variant="outline" onClick={() => startEditing(todo.id, todo.text)}>
<Edit2 className="w-4 h-4" />
</Button>
<Button size="icon" variant="outline" onClick={() => deleteTodo(todo.id)}>
<Trash2 className="w-4 h-4" />
</Button>
</div>
</>
)}
</div>
))}
</CardContent>
</Card>
</div>
);
};
export default TodoList;
第三步:逐步解释实现
1. State 管理
我通过 useState 钩子来管理以下四个状态:
todos:存储所有待办事项的数组,类型是Todo[]。newTodo:存储当前输入框中的新待办事项文本,类型是string。editingId:存储正在编辑的待办事项的 ID,如果没有正在编辑的事项,则为null。editText:存储当前正在编辑的文本,类型是string。
2. 添加待办事项
我实现了 addTodo 函数来向 todos 数组中添加新的待办事项。如果 newTodo 不为空,我就用 setTodos 来更新状态,将新的待办事项添加到数组中。
const addTodo = () => {
if (newTodo.trim() !== '') {
setTodos([...todos, { id: Date.now(), text: newTodo }]);
setNewTodo('');
}
};
3. 编辑待办事项
我实现了 startEditing、saveEdit 和 cancelEdit 函数来处理待办事项的编辑操作:
startEditing设置正在编辑的待办事项 ID 和文本。saveEdit保存编辑的文本并更新状态。cancelEdit取消编辑,恢复到原始状态。
4. 删除待办事项
我通过 deleteTodo 函数来删除指定 ID 的待办事项。通过 setTodos 更新状态,使用 filter 删除数组中对应的项。
5. 渲染待办事项
我通过 todos.map 来遍历所有待办事项,并渲染它们。对于每个待办事项:
- 如果该事项正在被编辑,显示编辑输入框和保存/取消按钮。
- 否则,显示待办事项文本并提供编辑和删除按钮。
总结
通过以上步骤,我实现了一个简单的待办事项列表,支持添加、编辑和删除功能,并且使用 TypeScript 增强了类型安全。在整个过程中,我使用了 React 的状态管理和事件处理机制,确保了应用的流畅性和可维护性。通过这些基本功能的实现,我对 React 和 TypeScript 的应用有了更深的理解,也为后续更复杂的功能打下了基础。