当前位置: 首页 > 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.dtcms.com/a/355959.html

相关文章:

  • C++(Qt)软件调试---vcpkg安装crashpad(34)
  • 金融Agent+LLM的特性分析与调研
  • C#并行计算(SIMD)应用
  • illustrator-02
  • 洛谷 P2568 GCD-提高+/省选−
  • 「Java EE开发指南」如何使用MyEclipse启用自动JSP验证?
  • C语言————函数递归(通俗易懂)
  • logcat 网络日志解析
  • 二、开关电源的EMC改善措施
  • Python Imaging Library (PIL) 全面指南:PIL基础入门-图像处理与数据预处理
  • Maven 编译打包一个比较有趣的问题
  • yolo学习笔记01——前置基础
  • 【力扣】2715. 执行可取消的延迟函数
  • 生产环境Vue组件报错:Cannot access before initialization
  • 将 agents 连接到 Elasticsearch 使用模型上下文协议 - docker
  • 前后端分离情况下,将本地vue项目和Laravel项目以及mysql放到自己的云服务器
  • 工业 5G + AI:智能制造的未来引擎
  • Mybatis-增删改查
  • 逻辑回归以及损失函数
  • 数字孪生(Digital Twin):未来产业与城市的智慧引擎
  • AI Agent从0到1:剖析Block与GSK的两种产品化落地路径
  • 容器学习04-kubernetes(k8s)
  • 海康相机开发---设备登录
  • (二分查找)Leetcode34. 在排序数组中查找元素的第一个和最后一个位置+74. 搜索二维矩阵
  • 【LInux】常用命令笔记
  • Linux之Shell编程(一)
  • 异步方法和多线程有什么区别,他们的实现逻辑是什么以及为什么异步方法: 不能和调用者在同一个类中
  • VisionPro联合编程控件导入WinFrom以及VS卡死问题
  • GCC版本和C语言标准版本的对应关系
  • 一个Demo射击小计(纯蓝图)