实践记录以及工具使用:使用 React实现待办事项列表 | 豆包MarsCode AI刷题

108 阅读4分钟

使用 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. 编辑待办事项

我实现了 startEditingsaveEditcancelEdit 函数来处理待办事项的编辑操作:

  • startEditing 设置正在编辑的待办事项 ID 和文本。
  • saveEdit 保存编辑的文本并更新状态。
  • cancelEdit 取消编辑,恢复到原始状态。
4. 删除待办事项

我通过 deleteTodo 函数来删除指定 ID 的待办事项。通过 setTodos 更新状态,使用 filter 删除数组中对应的项。

5. 渲染待办事项

我通过 todos.map 来遍历所有待办事项,并渲染它们。对于每个待办事项:

  • 如果该事项正在被编辑,显示编辑输入框和保存/取消按钮。
  • 否则,显示待办事项文本并提供编辑和删除按钮。

总结

通过以上步骤,我实现了一个简单的待办事项列表,支持添加、编辑和删除功能,并且使用 TypeScript 增强了类型安全。在整个过程中,我使用了 React 的状态管理和事件处理机制,确保了应用的流畅性和可维护性。通过这些基本功能的实现,我对 React 和 TypeScript 的应用有了更深的理解,也为后续更复杂的功能打下了基础。

截屏2024-11-30 19.35.26.png

截屏2024-11-30 19.35.59.png

截屏2024-11-30 19.36.21.png

截屏2024-11-30 19.37.09.png