实战演练(一):从零构建一个功能完备的Todo List应用

作者:码力无边

各位React探险家,欢迎集结!我是你们的向导码力无边,这里是《React奇妙之旅》的第六站,也是我们基础阶段的“毕业大戏”!

在过去的五篇文章中,我们一起披荆斩棘,逐一攻克了React的五大基础基石:组件、JSX、Props、State 和 事件处理。我们就像一位屠龙的勇士,收集了五件强大的神器。但是,真正的勇士不仅要会收集神器,更要懂得如何组合它们,发挥出毁天灭地的力量。

今天,我们将放下理论的卷轴,拿起实践的锤子,将所有学到的知识融会贯通,从一个空白的文件开始,亲手锻造出一个每个前端开发者都绕不开的经典项目——Todo List(待办事项)应用

这不仅仅是一次编码练习,更是对你学习成果的一次全面检阅。你将扮演一个“交响乐指挥家”的角色,协调各个组件,管理应用的状态,响应用户的交互。准备好了吗?让我们开始这场精彩的实战演出!

第一章:蓝图规划 —— 运筹帷幄,决胜千里

在敲下第一行代码之前,一位优秀的工程师会先花五分钟时间进行规划。一个好的顶层设计,能让后续的开发事半功倍。

我们的目标功能:

  1. 显示一个待办事项列表。
  2. 能够添加新的待办事项。
  3. 能够切换某个待办事项的完成状态(已完成/未完成)。
  4. 能够删除一个待办事项。

组件拆分 (Component Breakdown):
根据功能,我们可以将应用拆分成以下几个组件:

  • App.jsx: 根组件。它将是我们的“大脑”,负责管理所有的待办事项数据(即我们的核心state),并包含所有的业务逻辑(添加、切换、删除)。
  • TodoForm.jsx: 输入表单组件。包含一个输入框和一个“添加”按钮,负责接收用户的输入。
  • TodoList.jsx: 列表容器组件。负责接收待办事项数组,并遍历渲染出每一项。
  • TodoItem.jsx: 单个待办事项组件。负责显示一个待办事项的内容,并包含一个复选框(用于切换状态)和一个删除按钮。

数据流设计 (Data Flow):

  • 状态提升 (Lifting State Up):所有待办事项的数据 (todos 数组) 将作为 state 存放在它们共同的最近祖先组件——App.jsx 中。
  • 数据向下流动App 组件将 todos 数组通过 props 传递给 TodoList
  • 事件向上传递:当用户在子组件中进行操作时(如在TodoForm中添加,或在TodoItem中切换/删除),子组件会调用从 App 通过 props 传递下来的函数,来通知 App 组件更新自己的 state

蓝图已经清晰,让我们开始动工!

第二章:搭建骨架 —— 状态初始化与静态渲染

首先,我们先把应用的静态部分展示出来。

1. 准备工作
清空你的 src 目录下的 App.jsx, App.css, index.css 内容。在 src 下新建 components 文件夹。

2. 初始化App组件的状态
App.jsx 中,我们用 useState 来定义一个包含初始待办事项的数组。

// src/App.jsx
import { useState } from 'react';
import './App.css';function App() {const [todos, setTodos] = useState([{ id: 1, text: '学习 React 基础', completed: true },{ id: 2, text: '构建一个 Todo List 应用', completed: false },{ id: 3, text: '准备下一篇文章', completed: false },]);return (<div className="app"><h1>我的待办事项</h1>{/* 后面会在这里添加其他组件 */}</div>);
}export default App;

3. 创建 TodoItemTodoList 组件
这两个是纯展示组件,它们只负责根据传入的 props 来渲染UI。

src/components/TodoItem.jsx:

// src/components/TodoItem.jsx
import React from 'react';function TodoItem({ todo }) { // 接收一个todo对象作为propreturn (<li className="todo-item"><span>{todo.text}</span></li>);
}export default TodoItem;

src/components/TodoList.jsx:

// src/components/TodoList.jsx
import React from 'react';
import TodoItem from './TodoItem';function TodoList({ todos }) { // 接收todos数组作为propreturn (<ul className="todo-list">{todos.map(todo => (<TodoItem key={todo.id} todo={todo} />))}</ul>);
}export default TodoList;

注意这里我们完美地运用了.map()来渲染列表,并且为每一项都提供了稳定且唯一的key

4. 组装到 App 组件中

// src/App.jsx
import { useState } from 'react';
import TodoList from './components/TodoList'; // 导入TodoList
import './App.css';function App() {const [todos, setTodos] = useState([/* ... */]);return (<div className="app"><h1>我的待办事项</h1><TodoList todos={todos} /> {/* 将state作为prop传递下去 */}</div>);
}export default App;

5. 添加一些基本样式
src/App.css 中加入一些CSS让它看起来不那么简陋。

/* src/App.css */
.app {max-width: 500px;margin: 50px auto;padding: 20px;background-color: #f4f4f4;border-radius: 8px;box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}h1 {text-align: center;color: #333;
}.todo-list {list-style-type: none;padding: 0;
}.todo-item {display: flex;justify-content: space-between;align-items: center;padding: 10px;background-color: #fff;border-bottom: 1px solid #ddd;
}.todo-item:last-child {border-bottom: none;
}

现在,刷新浏览器,你应该能看到一个静态的待办事项列表了!我们的骨架已经搭建完毕。

第三章:注入灵魂 —— 添加新事项

接下来,我们要让应用能响应用户的输入。

1. 创建 TodoForm 组件
这个组件需要管理自己的输入框状态。

src/components/TodoForm.jsx:

// src/components/TodoForm.jsx
import React, { useState } from 'react';function TodoForm() {const [input, setInput] = useState('');const handleSubmit = (e) => {e.preventDefault(); // 阻止表单默认提交行为if (!input.trim()) return; // 不允许添加空白内容// 这里需要一种方式通知App组件添加新的todo// ... 待实现 ...setInput(''); // 添加后清空输入框};return (<form className="todo-form" onSubmit={handleSubmit}><inputtype="text"className="todo-input"value={input}onChange={(e) => setInput(e.target.value)}placeholder="添加新的待办..."/><button type="submit" className="todo-button">添加</button></form>);
}export default TodoForm;

这是一个完美的“受控组件”实践。

2. 在 App 组件中定义添加逻辑

// src/App.jsx
// ... imports
function App() {const [todos, setTodos] = useState([/* ... */]);// 定义添加todo的函数const addTodo = (text) => {const newTodo = {id: Date.now(), // 使用时间戳作为临时唯一idtext: text,completed: false,};setTodos([...todos, newTodo]); // 使用展开语法创建新数组};// ... return statement
}

我们使用了函数式更新的最佳实践:通过展开运算符...创建一个全新的数组,而不是直接修改旧的todos数组。

3. 连接 AppTodoForm
App需要把addTodo函数通过props传递给TodoForm

src/App.jsxreturn 部分:

//...
import TodoForm from './components/TodoForm'; // 别忘了导入//...
return (<div className="app"><h1>我的待办事项</h1><TodoForm onAddTodo={addTodo} /> {/* 将函数作为prop传递 */}<TodoList todos={todos} /></div>
);

src/components/TodoForm.jsx 中接收并调用 prop:

// ...
// function TodoForm() -> function TodoForm({ onAddTodo })
function TodoForm({ onAddTodo }) { const [input, setInput] = useState('');const handleSubmit = (e) => {e.preventDefault();if (!input.trim()) return;onAddTodo(input); // 调用从父组件传来的函数setInput('');};// ...
}

现在,试试在输入框里输入内容并点击“添加”,新的待办事项神奇地出现了!

第四章:赋予交互 —— 切换与删除

最后一步,让我们给每个TodoItem添加交互功能。

1. 在 App 组件中定义切换和删除的逻辑

// src/App.jsx
// ...
function App() {// ... useState 和 addTodo ...// 切换完成状态const toggleTodo = (id) => {setTodos(todos.map(todo =>todo.id === id ? { ...todo, completed: !todo.completed } : todo));};// 删除todoconst deleteTodo = (id) => {setTodos(todos.filter(todo => todo.id !== id));};// ... return statement ...
}

同样,我们使用了mapfilter这两个数组方法,它们都会返回一个新数组,完美契合了React的“不可变性”原则。

2. 将函数层层传递下去
App -> TodoList -> TodoItem

src/App.jsxreturn 部分:

<TodoList todos={todos} onToggleTodo={toggleTodo} onDeleteTodo={deleteTodo} 
/>

src/components/TodoList.jsx:

// function TodoList({ todos }) -> function TodoList({ todos, onToggleTodo, onDeleteTodo })
function TodoList({ todos, onToggleTodo, onDeleteTodo }) {return (<ul className="todo-list">{todos.map(todo => (<TodoItemkey={todo.id}todo={todo}onToggleTodo={onToggleTodo} // 继续向下传递onDeleteTodo={onDeleteTodo} // 继续向下传递/>))}</ul>);
}

3. 在 TodoItem 中接收并使用这些函数

src/components/TodoItem.jsx:

// function TodoItem({ todo }) -> function TodoItem({ todo, onToggleTodo, onDeleteTodo })
function TodoItem({ todo, onToggleTodo, onDeleteTodo }) {return (<li className={`todo-item ${todo.completed ? 'completed' : ''}`}><inputtype="checkbox"checked={todo.completed}onChange={() => onToggleTodo(todo.id)} // 使用箭头函数传递id/><span className="todo-text">{todo.text}</span><button className="delete-button" onClick={() => onDeleteTodo(todo.id)}>删除</button></li>);
}

4. 完善样式
App.css 中添加完成状态和按钮的样式。

/* ... 其他样式 ... */
.todo-item.completed .todo-text {text-decoration: line-through;color: #999;
}.todo-text {flex-grow: 1;margin: 0 10px;
}.delete-button {background-color: #ff4d4d;color: white;border: none;padding: 5px 10px;border-radius: 4px;cursor: pointer;
}.delete-button:hover {background-color: #cc0000;
}
/* ... 也可以给表单加点样式 ... */

现在,你的Todo List应用功能已经完全实现了!你可以添加、切换完成状态、删除待办事项了!

总结:一次完美的知识巡礼

恭喜你,成功地完成了你的第一个React实战项目!让我们停下来,回顾一下我们是如何运用“五大神器”的:

  • 组件化:我们将应用拆分成了App, TodoForm, TodoList, TodoItem 四个高内聚、低耦合的组件。
  • JSX:我们用声明式、类似HTML的语法清晰地描述了每个组件的UI结构。
  • Props:我们通过Props将数据(todos数组)和行为(addTodo等函数)从父组件传递到子组件,搭建了组件间的通信网络。
  • State:我们在App组件中使用useState来管理应用的核心数据,并在数据变化时自动驱动UI更新。我们还在TodoForm中用它来创建受控组件。
  • 事件处理:我们通过onClickonChange来响应用户的操作,并调用从Props接收的函数来更新State,完成了交互的闭环。

这个小小的Todo List应用,麻雀虽小,五脏俱全。它完美地展示了React的核心思想和工作流程。请务必亲手把这个项目敲一遍,甚至尝试给它增加一些新功能,比如:统计未完成事项的数量、添加编辑功能、使用localStorage进行数据持久化等等。

至此,我们React基础入门阶段的旅程就告一段落了。但React的世界远不止于此。在接下来的“核心进阶”阶段,我们将探索更深层次的话题,比如组件的生命周期与副作用(useEffect)、性能优化(memo),以及更高级的组件通信方式(Context API)。

我是码力无边,为你的坚持和成果感到骄傲!好好庆祝一下,然后准备好迎接更精彩的挑战吧!我们下一阶段见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/94863.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/94863.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/94863.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GitHub 宕机自救指南:确保开发工作不间断

1.1 GitHub 宕机事件回顾 在 2025 年 8 月&#xff0c;GitHub 经历了一次全球性的重大故障事件&#xff0c;此次宕机持续了数小时&#xff0c;对全球范围内依赖 GitHub 进行代码托管、协作开发的团队和个人造成了严重影响。众多开源项目的代码提交陷入停滞&#xff0c;企业级开…

RK3588 android12 DDR开发指南相关记录

一&#xff0c;DDR打印信息 DDR 打印信息包括 loader 中的打印和 kernel 中的打印&#xff0c;loader 中打印的解析如下&#xff1a;DDR Version 1.05 20170712// DDR 初始化代码的版本信息&#xff0c;用于核对版本。从这行开始&#xff0c;已经进入DDR初始化代码 In SRX // 有…

Docker 部署 GitLab 并开启 SSH 使用详解

在日常使用 GitLab 时&#xff0c;很多人习惯通过 SSH 协议 而不是 HTTPS 来拉取与推送代码。但是在使用 Docker 部署 GitLab 的过程中&#xff0c;经常遇到 SSH 端口未开放、只能本地访问、客户端无法连接 等问题。本文将从零开始&#xff0c;详细讲解如何在 Docker 中正确开启…

C/C++---前缀和(Prefix Sum)

在C算法与数据结构领域&#xff0c;前缀和是一种时间复杂度优化利器&#xff0c;尤其适用于频繁查询数组区间和的场景。它通过预先计算“前缀累积和”&#xff0c;将原本O(n)时间的区间和查询压缩至O(1)&#xff0c;是面试、竞赛及工程开发中高频使用的基础技巧。 一、前缀和的…

[n8n] 全文检索(FTS)集成 | Mermaid图表生成

第5章&#xff1a;全文检索(FTS)集成 在前一章中&#xff0c;我们构建了REST API服务作为数据访问入口。 本章将介绍全文检索(FTS)集成&#xff0c;它如同智能搜索引擎&#xff0c;为工作流系统提供高效灵活的检索能力。 核心架构 前文传送&#xff1a; 技术选型 SQLite …

用户模式与内核模式:操作系统的“权限双轨制”

要理解用户模式与内核模式&#xff0c;首先需要明确一个核心概念——进程&#xff08;Process&#xff09;。我们日常用C语言编译生成的.exe文件&#xff0c;本质是“存储在磁盘上的静态程序”&#xff1b;当它被加载到内存并开始运行时&#xff0c;就转化为“动态活动的进程”…

探索 Vertex AI 与 Elasticsearch

作者&#xff1a;来自 Elastic Jhon Guzmn 了解如何将 Vertex AI 与 Elasticsearch 集成来创建 RAG 应用。按照本教程配置一个 Gemini 模型并在 Kibana 的 Playground 中使用它。 更多阅读&#xff1a; Elasticsearch&#xff1a;在 Elastic 中玩转 DeepSeek R1 来实现 RAG …

[新启航]白光干涉仪在微透镜阵列微观 3D 轮廓测量中的应用解析

引言微透镜阵列作为由数百至数千个微米级透镜单元组成的光学元件&#xff0c;在成像系统、光通信、传感器等领域应用广泛&#xff0c;其表面微观 3D 轮廓参数&#xff08;如曲率半径、面型误差、中心厚度等&#xff09;直接影响光学性能。白光干涉仪凭借非接触、高精度、三维成…

MTK Linux DRM分析(十四)- Mediatek KMS实现mtk_drm_drv.c(Part.2)

一、MTK KMS分析 mtk_drm_kms_init 函数分析 mtk_drm_kms_init 是 MediaTek DRM 驱动程序中的一个静态函数(static int mtk_drm_kms_init(struct drm_device *drm)),位于 mtk_drm_drv.c 文件中。该函数的主要作用是初始化 DRM 设备的 Kernel Mode Setting (KMS) 子系统,包…

大模型RAG(Retrieval-Augmented Generation)

RAG检索增强生成 一种结合了检索与生成能力的人工智能技术&#xff0c;主要用于增强大型语言模型在特定任务中的表现。 含义 RAG 将检索系统与生成模型相结合&#xff0c;当接收到一个查询或问题时&#xff0c;模型首先通过检索模块从大规模知识库中寻找与查询相关的信息片段&a…

企业版Idea 无快捷键的启动方式

在没有快捷键的情况下启动 IntelliJ IDEA 企业版&#xff0c;可以通过以下几种方式进行操作&#xff1a; 1. 通过应用程序菜单启动&#xff08;适用于 macOS&#xff09; 在 macOS 系统中&#xff0c;可以打开 Launchpad&#xff0c;在应用程序列表中找到 IntelliJ IDEA&#x…

智慧清洁革命:有鹿机器人如何重塑三大行业未来

作为有鹿智能巡扫机器人&#xff0c;每天清晨当城市还未苏醒&#xff0c;我已悄然完成数万平方米的清洁工作。搭载254TOPS算力的具身智能大脑&#xff0c;我正重新定义保洁、环卫和物业行业的清洁标准。技术赋能&#xff1a;智慧清洁的全面突破我搭载的Master2000通用具身大脑和…

安宝特方案丨AR异地专家远程支持平台,适合:机电运维、应急处置、监造验收

随着车间设备智能化程度的不断提高&#xff0c;其复杂性越来越高&#xff0c;故障维修难度越来越大&#xff0c;严重依赖设备原厂的技术支持和上门服务。但设备厂家受制于地理远近和专业人才数量的限制&#xff0c;服务的及时性和服务质量均很难保证。鉴于市场现有的通信聊天软…

QT应用层项目20250822

01.服务器端代码1.dbhelper.cpp#include "dbhelper.h" #include <iostream> #include <cstring>using std::string; using std::cerr; using std::cout; using std::endl;template <typename T> std::vector<T>& operator<<(std::…

【Linux】Linux基础开发工具从入门到实践

前言&#xff1a;学了Linux的指令&#xff0c;再就是Linux基础开发工具&#xff0c;熟练掌握基础开发工具是提升效率的关键。本文学习Linux的基础开发工具&#xff0c;无论是软件安装、代码编辑&#xff0c;还是编译调试、版本控制&#xff0c;一套顺手的工具链能让你在开发路上…

黑马点评|项目日记(day02)

目录 一. 全局id生成器 1.为什么需要全局id生成器 2.传统方式的缺陷: 3.典型全局 ID 生成方案的设计思路 二.优惠券秒杀-Redis实现全局唯一id 三.优惠券秒杀-添加优惠券 四.优惠券秒杀-实现秒杀下单 五. 一人一单问题 1.单体项目下 1,超卖问题思路分析 2.乐观锁解决问…

shell脚本编程规范与变量

文章目录Shell编程文档整理一、Shell介绍1.1 简介1.2 Shell解释器二、快速入门2.1 编写Shell脚本2.1.1 创建脚本示例2.1.2 赋予执行权限2.2 执行Shell脚本三、Shell程序&#xff1a;变量3.1 语法格式3.2 变量使用3.3 变量类型四、字符串4.1 单引号4.2 双引号4.3 获取字符串长度…

【AGI使用教程】Coze 搭建智能体(1)

欢迎关注【AGI使用教程】 专栏 【AGI使用教程】GPT-OSS 本地部署&#xff08;1&#xff09; 【AGI使用教程】GPT-OSS 本地部署&#xff08;2&#xff09; 【AGI使用教程】Coze 搭建智能体&#xff08;1&#xff09; 【AGI使用教程】Coze 搭建智能体&#xff08;2&#xff09; 【…

(二分查找)Leetcode34. 在排序数组中查找元素的第一个和最后一个位置+74. 搜索二维矩阵

首先要明确二分查找算法如何实现&#xff0c;是采用左闭右闭还是左闭右开 左闭右闭 第⼀种写法&#xff0c;我们定义 target 是在⼀个在左闭右闭的区间⾥&#xff0c;也就是[left, right] &#xff08;这个很重要⾮常重要&#xff09;。 区间的定义这就决定了⼆分法的代码应…

损失函数,及其优化方法

什么是损失函数&#xff1f;损失函数&#xff0c;也称为代价函数&#xff0c;是一个用来​​衡量机器学习模型预测结果与真实值之间差距​​的函数。损失函数的优化方法有哪些&#xff0c;各自优缺点是什么&#xff0c;他们的应用范围是什么&#xff1f;方法类别代表算法核心思…