摘要:在本教程中,您将学习如何逐步构建一个 React 待办事项应用程序。
React 待办事项应用程序概述
这个 React 待办事项应用程序 允许您添加新的待办事项,编辑现有待办事项,删除待办事项,并将待办事项标记为已完成。
以下图片显示了我们将要构建的应用程序

当您输入标题并按 Enter(或 Return)键时,它将创建一个新的待办事项并将其添加到列表中。
单击编辑按钮将显示一个表单,允许您修改待办事项。此外,单击删除按钮将从列表中删除待办事项。
当您双击待办事项时,该应用程序将将其标记为已完成,并对待办事项标题应用删除线。
组件层次结构
以下显示了待办事项应用程序的组件层次结构
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
组件通过 TodoList
和 TodoShow
组件传递下来的 changeTodo
函数来更新待办事项的标题。
更改完成后,TodoEdit
应该显示 TodoShow
组件。
创建一个新的 React 待办事项应用程序
步骤 1. 打开终端并运行以下命令以创建一个新的 React 应用程序
npx create-react-app todo
Code language: JavaScript (javascript)
步骤 2. 删除 src
目录中的所有文件。
步骤 3. 在项目目录下创建以下 components
目录。我们将把所有 React 组件存储在 components
目录中。
步骤 4. 创建以下新文件
文件 | 目录 | 描述 |
---|---|---|
index.js | src | React 应用程序的入口文件 |
App.js | src | App 组件 |
App.css | src | CSS 文件 |
TodoCreate.js | src/components | TodoCreate 组件 |
TodoList.js | src/components | TodoList 组件 |
TodoShow.js | src/components | TodoShow 组件 |
TodoEdit.js | src/components | TodoEdit 组件 |
请在教程末尾的项目源代码中使用 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
组件使用 TodoList
和 TodoCreate
组件。
步骤 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 start
Code language: JavaScript (javascript)
应用程序将看起来像以下内容

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()
函数中
首先,创建一个具有三个属性 id
、title
和 completed
的新的待办事项对象。
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
对象的 title
和 completed
属性
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)
handleEdit
将 showEdit
状态设置为 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)
第三,将 handleDelete
和 handleEdit
事件处理程序添加到相应按钮的 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)