React 待办事项应用程序

摘要:在本教程中,您将学习如何逐步构建一个 React 待办事项应用程序。

React 待办事项应用程序概述

这个 React 待办事项应用程序 允许您添加新的待办事项,编辑现有待办事项,删除待办事项,并将待办事项标记为已完成。

以下图片显示了我们将要构建的应用程序

React Todo App

当您输入标题并按 Enter(或 Return)键时,它将创建一个新的待办事项并将其添加到列表中。

单击编辑按钮将显示一个表单,允许您修改待办事项。此外,单击删除按钮将从列表中删除待办事项。

当您双击待办事项时,该应用程序将将其标记为已完成,并对待办事项标题应用删除线。

组件层次结构

以下显示了待办事项应用程序的组件层次结构

React todo app - component hierarchy
  • TodoCreate - 将新的待办事项添加到列表中。
  • TodoList - 显示待办事项列表。
  • TodoShow - 显示单个待办事项。
  • TodoEdit - 编辑待办事项。

App 存储应用程序的状态,即待办事项列表。

TodoCreate 组件

TodoCreate 组件用于将新的待办事项添加到列表中。它是通过执行从 App 组件传递下来的 createTodo 函数来实现的。

createTodo 函数将新的待办事项添加到 App 组件中的待办事项列表中。

TodoList 组件

TodoList 组件显示待办事项列表。它使用从 App 组件传递下来的待办事项列表来渲染一系列 TodoShow 组件。

TodoShow 组件

TodoShow 组件渲染每个待办事项。

双击待办事项的标题会切换其状态,在已完成和未完成之间切换。TodoShow 通过执行从 App 组件通过 TodoList 组件传递下来的 changeTodo 函数来更改待办事项的状态。

单击 TodoShow 组件中的删除按钮将从列表中删除待办事项。这是通过执行从 App 组件通过 TodoList 组件传递下来的 removeTodo 回调来完成的。

单击编辑按钮将显示 TodoEdit 组件。

TodoEdit 组件

TodoEdit 组件通过执行从 App 组件通过 TodoListTodoShow 组件传递下来的 changeTodo 函数来更新待办事项的标题。

更改完成后,TodoEdit 应该显示 TodoShow 组件。

创建一个新的 React 待办事项应用程序

步骤 1. 打开终端并运行以下命令以创建一个新的 React 应用程序

npx create-react-app todoCode language: JavaScript (javascript)

步骤 2. 删除 src 目录中的所有文件。

步骤 3. 在项目目录下创建以下 components 目录。我们将把所有 React 组件存储在 components 目录中。

步骤 4. 创建以下新文件

文件目录描述
index.jssrcReact 应用程序的入口文件
App.jssrcApp 组件
App.csssrcCSS 文件
TodoCreate.jssrc/componentsTodoCreate 组件
TodoList.jssrc/componentsTodoList 组件
TodoShow.jssrc/componentsTodoShow 组件
TodoEdit.jssrc/componentsTodoEdit 组件

请在教程末尾的项目源代码中使用 SVG 图标文件和 App.css 文件。

步骤 5. 将以下代码添加到 index.js 文件中

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const el = document.querySelector('#root');
const root = ReactDOM.createRoot(el);

root.render(<App />);Code language: JavaScript (javascript)

index.js 文件在屏幕上渲染 App 组件。

步骤 6. 将以下代码添加到 App.js 文件中

import TodoList from './components/TodoList';
import TodoCreate from './components/TodoCreate';
import './App.css';

const App = () => {
  return (
    <main className="main">
      <h1>
        React Todo <span>Streamline Your Day, the React Way!</span>
      </h1>
      <TodoList />
      <TodoCreate />
    </main>
  );
};

export default App;Code language: JavaScript (javascript)

App 组件使用 TodoListTodoCreate 组件。

步骤 7. 将以下代码添加到 TodoCreate 组件中

const TodoCreate = () => {
  return <div>Todo Create</div>;
};

export default TodoCreate;Code language: JavaScript (javascript)

步骤 7. 将以下代码添加到 TodoList 组件中

const TodoList = () => {
  return <div>Todo List</div>;
};

export default TodoList;Code language: JavaScript (javascript)

步骤 8. 将以下代码添加到 TodoEdit 组件中

const TodoEdit = () => {
  return <div>Todo Edit</div>;
};

export default TodoEdit;Code language: JavaScript (javascript)

步骤 9. 将以下代码添加到 TodoShow 组件中

const TodoShow = () => {
  return <div>Todo Show </div>;
};

export default TodoShow;Code language: JavaScript (javascript)

步骤 10. 在终端中执行以下命令以运行应用程序

npm startCode language: JavaScript (javascript)

应用程序将看起来像以下内容

React Todo App Initial

App 组件

步骤 1. 将 todos 数组定义为 App 组件的状态

const [todos, setTodos] = useState([]);Code language: JavaScript (javascript)

默认情况下,todos 状态为空。

步骤 2. 定义一个函数,该函数将新的待办事项添加到 todos 列表中

const createTodo = (title) => {
  const newTodo = { id: crypto.randomUUID(), title: title, completed: false };
  const updatedTodos = [...todos, newTodo];
  setTodos(updatedTodos);
};Code language: JavaScript (javascript)

createTodo() 函数中

首先,创建一个具有三个属性 idtitlecompleted 的新的待办事项对象。

id 获取由 Web 浏览器提供的 crypto.randomUUID() 方法返回的 UUID 值。completed 属性的值默认为 false

其次,创建一个包含新创建的待办事项的新数组

const updatedTodos = [...todos, newTodo];Code language: JavaScript (javascript)

请注意,如果您使用 push() 方法将新的待办事项添加到 todos 数组中,则只有 todos 数组的项目会发生变化,但 todos 数组引用是相同的。因此,React 不会重新渲染组件,因为其状态没有改变。

第三,使用 setTodos() 函数更新 todos 数组,使其包含新的数组

setTodos(updatedTodos);Code language: JavaScript (javascript)

此函数调用将导致组件重新渲染。

步骤 3. 定义一个 removeTodo() 函数,该函数通过 id 从待办事项列表中删除待办事项

const removeTodo = (id) => {
  const updatedTodos = todos.filter((todo) => todo.id !== id);
  setTodos(updatedTodos);
};Code language: JavaScript (javascript)

removeTodo() 函数中

首先,使用 filter() 数组方法返回一个不包含指定 id 的待办事项的新数组

const updatedTodos = todos.filter((todo) => todo.id !== id);Code language: JavaScript (javascript)

其次,使用新数组更新 todos 状态

setTodos(updatedTodos);Code language: JavaScript (javascript)

步骤 4. 定义 changeTodo 函数,该函数更改由 id 指定的 todo 对象的 titlecompleted 属性

const changeTodo = (id, title, completed = false) => {
  const updatedTodos = todos.map((todo) => {
    if (todo.id === id) {
      return { ...todo, title, completed };
    }
    return todo;
  });

  setTodos(updatedTodos);
};Code language: JavaScript (javascript)

步骤 5. 将 todos 数组、removeTodo 函数和 changeTodo 函数作为属性传递给 TodoList 组件,并将 createTodo 函数作为 TodoCreate 组件的属性传递

<TodoList todos={todos} removeTodo={removeTodo} changeTodo={changeTodo} />
<TodoCreate createTodo={createTodo} />Code language: JavaScript (javascript)

以下是完整的 App 组件

import TodoList from './components/TodoList';
import TodoCreate from './components/TodoCreate';
import './App.css';
import { useState } from 'react';

const App = () => {
  const [todos, setTodos] = useState([]);

  const createTodo = (title) => {
    const newTodo = { id: crypto.randomUUID(), title, completed: false };
    const updatedTodos = [...todos, newTodo];
    setTodos(updatedTodos);
  };

  const removeTodo = (id) => {
    const updatedTodos = todos.filter((todo) => todo.id !== id);
    setTodos(updatedTodos);
  };

  const changeTodo = (id, title, completed = false) => {
    const updatedTodos = todos.map((todo) => {
      if (todo.id === id) {
        return { ...todo, title, completed };
      }
      return todo;
    });

    setTodos(updatedTodos);
  };

  return (
    <main className="main">
      <h1>
        React Todo <span>Streamline Your Day, the React Way!</span>
      </h1>
      <TodoList todos={todos} removeTodo={removeTodo} changeTodo={changeTodo} />
      <TodoCreate createTodo={createTodo} />
    </main>
  );
};

export default App;Code language: JavaScript (javascript)

TodoCreate 组件

修改 TodoCreate 组件。

步骤 1. 将 createTodo 函数作为 TodoCreate 组件的参数添加

const TodoCreate = ({ createTodo }) => {
    // ...
}Code language: JavaScript (javascript)

步骤 2. 返回一个包含输入待办事项标题的输入字段的表单

return (
  <form className="todo-create">
    <input
      type="text"
      name="title"
      id="title"
      placeholder="Enter a todo"
    />
  </form>
);Code language: JavaScript (javascript)

步骤 3. 定义一个状态,该状态保存输入字段的标题,并将其值初始化为空字符串

const [title, setTitle] = useState('');Code language: JavaScript (javascript)

步骤 4. 定义一个 handleChange 函数,该函数将标题状态更新为输入字段的当前值

const handleChange = (e) => {
  setTitle(e.target.value);
};Code language: JavaScript (javascript)

步骤 5. 将标题状态和 handleChange 事件处理程序用于输入字段

<input
  type="text"
  name="title"
  id="title"
  placeholder="Enter a todo"
  value={title}
  onChange={handleChange}
/>;Code language: JavaScript (javascript)

步骤 6. 定义一个 handleSubmit 函数,该函数在表单提交时执行

const handleSubmit = (e) => {
  e.preventDefault();
  createTodo(title);
  setTitle('');
};Code language: JavaScript (javascript)

handleSubmit() 函数中

首先,调用事件对象的 preventDefault() 方法来阻止页面的重新加载

e.preventDefault();Code language: JavaScript (javascript)

其次,调用 createTodo 函数以创建新的待办事项并将其添加到 App 组件中的 todos 状态中。

第三,通过将标题状态设置为一个空字符串来重置输入字段为空白

setTitle('');Code language: JavaScript (javascript)

以下是完整的 TodoCreate 组件

import { useState } from 'react';

const TodoCreate = ({ createTodo }) => {
  const [title, setTitle] = useState('');

  const handleChange = (e) => {
    setTitle(e.target.value);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    createTodo(title);
    setTitle('');
  };

  return (
    <form onSubmit={handleSubmit} className="todo-create">
      <input
        type="text"
        name="title"
        id="title"
        placeholder="Enter a todo"
        value={title}
        onChange={handleChange}
      />
    </form>
  );
};

export default TodoCreate;Code language: JavaScript (javascript)

如果您输入一个待办事项并按 Enter 键,则会创建新的待办事项并将其添加到待办事项列表中。但是,在修改 TodoList 组件之前,您将无法看到它。

TodoCreate 组件

步骤 1. 将 todos、removeTodo 函数和 changeTodo 函数作为 TodoList 组件的属性添加

const TodoList = ({ todos, removeTodo, changeTodo }) => {
   // ...
}Code language: JavaScript (javascript)

步骤 2. 基于 todos 数组生成一系列 TodoShow 组件。此外,将待办事项对象、removeTodo 函数和 changeTodo 函数传递给 TodoShow 组件

const renderedTodos = todos.map((todo) => {
  return (
    <TodoShow
      key={todo.id}
      todo={todo}
      removeTodo={removeTodo}
      changeTodo={changeTodo}
    />
  );
});Code language: JavaScript (javascript)

步骤 3. 渲染待办事项列表

return <ul className="todo-list">{renderedTodos}</ul>;Code language: JavaScript (javascript)

以下是完整的 TodoList 组件

import TodoShow from './TodoShow';

const TodoList = ({ todos, removeTodo, changeTodo }) => {
  const renderedTodos = todos.map((todo) => {
    return (
      <TodoShow
        key={todo.id}
        todo={todo}
        removeTodo={removeTodo}
        changeTodo={changeTodo}
      />
    );
  });

  return <ul className="todo-list">{renderedTodos}</ul>;
};

export default TodoList;Code language: JavaScript (javascript)

TodoShow 组件

步骤 1. 将待办事项对象、removeTodo 函数和 changeTodo 函数作为 TodoShow 组件的属性添加

const TodoShow = ({ todo, removeTodo, changeTodo }) => {
   // ..
}Code language: JavaScript (javascript)

步骤 2. 定义 ShowEdit 状态,当它为 true 时显示 TodoEdit 组件

const [showEdit, setShowEdit] = useState(false);Code language: JavaScript (javascript)

默认情况下,TodoEdit 组件是隐藏的。

步骤 3. 定义一个事件处理程序 handleDelete,该处理程序在用户单击删除按钮时执行

const handleDelete = (e) => {
  removeTodo(todo.id);
};Code language: JavaScript (javascript)

步骤 4. 定义一个事件处理程序 handleEdit,该处理程序在用户单击编辑按钮时执行

const handleEdit = (e) => {
  setShowEdit(true);
};Code language: JavaScript (javascript)

handleEditshowEdit 状态设置为 true 以显示 TodoEdit 组件。

步骤 5. 删除一个事件处理程序 handleDoubleClick,该处理程序在用户双击待办事项时运行

const handleDoubleClick = (e) => {
  changeTodo(todo.id, todo.title, !todo.completed);
};Code language: JavaScript (javascript)

handleDoubleClick 调用 changeTodo 函数以切换 completed 属性的值。

步骤 6. 定义一个事件处理程序 handleSubmit,该处理程序在 TodoEdit 组件上的表单提交时执行

const handleSubmit = (id, title) => {
  changeTodo(id, title);
  setShowEdit(false);
};Code language: JavaScript (javascript)

工作原理。

首先,通过调用 changeTodo 函数根据 id 更改待办事项的标题

changeTodo(id, title);Code language: JavaScript (javascript)

其次,通过将 showEdit 状态设置为 false 来隐藏 TodoEdit 组件

setShowEdit(false);Code language: JavaScript (javascript)

步骤 7. 如果 showEdit 为 true,则渲染 TodoEdit 组件

if (showEdit) {
  return (
    <li className="todo">
      <TodoEdit todo={todo} onSubmit={handleSubmit} />
    </li>
  );
}Code language: JavaScript (javascript)

在这段代码中,我们将待办事项对象和 handleSubmit 函数传递给 TodoEdit 组件。

步骤 8. 渲染待办事项

return (
  <li className="todo" onDoubleClick={handleDoubleClick}>
    <p className={todo.completed && 'completed'}>{todo.title}</p>

    <div className="actions">
      <button onClick={handleDelete}>
        <img src={DeleteIcon} title="Delete" />
      </button>
      <button onClick={handleEdit}>
        <img src={EditIcon} title="Edit" />
      </button>
    </div>
  </li>
);Code language: JavaScript (javascript)

工作原理。

首先,将 handleDoubleClick 事件处理程序添加到 li 元素的 onDoubleClick 事件中。

<li className="todo" onDoubleClick={handleDoubleClick}>Code language: JavaScript (javascript)

其次,如果 todo 对象的 completed 属性为 true,则将 completed CSS 类添加到 <p> 元素中

<p className={todo.completed && 'completed'}>{todo.title}</p>Code language: JavaScript (javascript)

第三,将 handleDeletehandleEdit 事件处理程序添加到相应按钮的 onClick 事件中

<div className="actions">
  <button onClick={handleDelete}>
    <img src={DeleteIcon} title="Delete" />
  </button>
  <button onClick={handleEdit}>
    <img src={EditIcon} title="Edit" />
  </button>
</div>;Code language: JavaScript (javascript)

TodoEdit 组件

首先,将待办事项对象和 onSubmit 函数作为 TodoEdit 组件的属性添加

const TodoEdit = ({ todo, onSubmit }) => {
   // ...
};Code language: JavaScript (javascript)

步骤 2. 定义 TodoEdit 组件的标题状态,并将其值初始化为待办事项对象的标题

const [title, setTitle] = useState(todo.title);Code language: JavaScript (javascript)

步骤 3. 定义一个事件处理程序,该处理程序在用户通过编辑表单更改标题时运行

const handleChange = (e) => {
  setTitle(e.target.value);
};Code language: JavaScript (javascript)

handleChange 函数调用 setTitle 函数以将标题状态更新为输入元素的当前值。

步骤 4. 定义一个事件处理程序,该处理程序在用户提交表单时运行

const handleSubmit = (e) => {
  e.preventDefault();
  onSubmit(todo.id, title);
};Code language: JavaScript (javascript)

首先,调用事件对象的 preventDefault() 方法来阻止页面重新加载

e.preventDefault();Code language: JavaScript (javascript)

第二步,调用onSubmit函数,更新待办事项的title并隐藏TodoEdit组件。

onSubmit(todo.id, title);Code language: JavaScript (javascript)

步骤 5. 渲染表单并连接事件处理程序。

return (
  <form className="todo-edit">
    <input type="text" value={title} onChange={handleChange} />
    <button type="submit" onClick={handleSubmit}>
      <img src={CheckIcon} title="Save" />
    </button>
  </form>
);Code language: JavaScript (javascript)

以下是完整的TodoEdit组件。

import { useState } from 'react';
import CheckIcon from '../check.svg';

const TodoEdit = ({ todo, onSubmit }) => {
  const [title, setTitle] = useState(todo.title);

  const handleChange = (e) => {
    setTitle(e.target.value);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    onSubmit(todo.id, title);
  };

  return (
    <form className="todo-edit">
      <input type="text" value={title} onChange={handleChange} />
      <button type="submit" onClick={handleSubmit}>
        <img src={CheckIcon} title="Save" />
      </button>
    </form>
  );
};

export default TodoEdit;Code language: JavaScript (javascript)

下载 React 待办事项应用程序源代码。

本教程对您有帮助吗?