当前位置: 首页 > news >正文

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

实战演练(一):从零构建一个功能完备的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.xdnf.cn/news/1382509.html

相关文章:

  • Spring事务管理机制深度解析:从JDBC基础到Spring高级实现
  • 力扣(LeetCode) ——965. 单值二叉树(C语言)
  • C#写的一键自动测灯带的应用 AI帮写的。
  • [灵动微电子 MM32BIN560CN MM32SPIN0280]读懂电机MCU之串口DMA
  • list 手动实现 1
  • 学习日志40 python
  • 微服务即时通信系统(十三)--- 项目部署
  • 【后端】微服务后端鉴权方案
  • 虚函数指针和虚函数表的创建时机和存放位置
  • 【Linux知识】Linux 设置账号密码永不过期
  • 完整代码注释:实现 Qt 的 TCP 客户端,实现和服务器通信
  • 【LINUX网络】TCP原理
  • WEEX唯客上线C2C交易平台:打造安全便捷的用户交易体验
  • 现在购买PCIe 5.0 SSD是否是最好的时机?
  • 前端实现Linux查询平台:打造高效运维工作流
  • [光学原理与应用-320]:光学产品不同阶段使用的工具软件、对应的输出文件
  • 华为S5720S重置密码
  • c语言动态数组扩容
  • MCU平台化实践方案
  • STL库——list(类函数学习)
  • 财务数据报销画像技术实现:从数据采集到智能决策的全流程解析
  • 【AI自动化】VSCode+Playwright+codegen+nodejs自动化脚本生成
  • 当new一块内存时,操作系统做了哪些事情
  • 软考 系统架构设计师系列知识点之杂项集萃(134)
  • leetcode算法刷题的第二十天
  • 鸿蒙OS与Rust整合开发流程
  • 面试tips--JVM(3)--类加载过程
  • 动态加载和异步调用tasklet/workqueue day63 ay64
  • 中国剩余定理(以及扩展..)
  • .Net Core Web 架构(管道机制)的底层实现