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

React 实战: Todo 应用学习小结

🎯 项目完成情况

成功开发了一个功能完整的 Todo 应用,具备增删改查、状态切换和实时统计功能。

📚 核心概念掌握

1. useState - 状态管理

// 基础状态
const [taskInput, setTaskInput] = useState("");
const [tasks, setTasks] = useState([]);// 编辑相关状态
const [editingId, setEditingId] = useState(null);
const [editingText, setEditingText] = useState("");

关键理解:状态驱动 UI 更新,状态改变 → 组件重新渲染补充细节

  • useState 是一个 Hook,必须在函数组件的顶层调用,不能在条件语句、循环或嵌套函数中使用
  • 初始值只在组件第一次渲染时生效,后续渲染会被忽略
  • 状态更新是异步的,React 可能会批量处理多个状态更新以提高性能
  • 对于引用类型(对象、数组),直接修改不会触发重新渲染,必须创建新的引用

2. 数组状态更新模式(重要!)

// 添加:[...原数组, 新元素]
setTasks([...tasks, newTask]);// 删除:filter过滤
setTasks(tasks.filter(task => task.id !== id));// 修改:map遍历修改特定项
setTasks(tasks.map(task => task.id === id ? { ...task, 属性: 新值 } : task
));

补充细节

  • 这些方法都遵循了 "不可变数据" 原则,不会直接修改原数组
  • filter 会返回一个新数组,包含所有符合条件的元素
  • map 会返回一个新数组,每个元素都是经过处理的原数组元素
  • 对于复杂数组操作,可以考虑使用 immer 库简化代码
  • 数组长度为 0 时(空数组),filter 会返回空数组,map 不会执行回调函数

3. 事件处理

// 点击事件
onClick={() => handleFunction(param)}// 输入变化
onChange={(e) => setState(e.target.value)}// 键盘事件(React 18用onKeyDown)
onKeyDown={(e) => e.key === 'Enter' && handleSave()}

补充细节

  • React 事件是合成事件(SyntheticEvent),不是原生 DOM 事件,但拥有类似的 API
  • 事件处理函数中的 this 默认是 undefined,所以通常使用箭头函数绑定上下文
  • 传递参数时,使用箭头函数包裹是最常见的方式,如 onClick={() => handleDelete(id)}
  • 可以通过 e.preventDefault() 阻止默认行为(如表单提交),e.stopPropagation() 阻止事件冒泡
  • 对于频繁触发的事件(如 onScrollonResize),考虑使用防抖节流优化性能

4. 条件渲染

// 条件显示不同内容
{条件 ? 内容A : 内容B}// 根据状态添加CSS类
className={`基础类 ${条件 ? '特殊类' : ''}`}// 根据状态显示不同按钮
{editingId === task.id ? 编辑按钮组 : 正常按钮组}

补充细节

  • 除了三元表达式,还可以使用逻辑与 && 进行条件渲染:{条件 && 内容}
  • 逻辑与 && 渲染规则:条件为真时渲染后面的内容,条件为假时渲染 false(React 会忽略 falsenullundefined 和 true
  • 可以将条件渲染逻辑抽取到函数中,提高代码可读性
  • 对于复杂的条件渲染,可以使用变量存储要渲染的内容
  • 避免在条件渲染中使用不同类型的元素作为同一位置的子元素,这会导致 React 重新创建 DOM 节点而非更新

🔧 具体功能实现

添加任务

const handleAddTask = () => {if (!taskInput.trim()) return;const newTask = {id: Date.now(),        // 简单ID生成text: taskInput.trim(),completed: false       // 默认未完成};setTasks([...tasks, newTask]);setTaskInput("");        // 清空输入框
};

补充细节

  • Date.now() 生成的 ID 在小型应用中足够用,但在并发添加任务时可能会有冲突
  • 更可靠的 ID 生成方案:可以使用 uuid 库,或结合时间戳与随机数
  • trim() 方法用于去除输入文本前后的空格,避免创建空任务或只有空格的任务
  • 可以添加任务重复检查,避免创建相同内容的任务

切换完成状态

const toggleTask = (id) => {setTasks(tasks.map(task => task.id === id ? { ...task, completed: !task.completed } : task));
};
// !操作符:取反布尔值

补充细节

  • 使用对象展开运算符 ...task 确保只修改 completed 属性,其他属性保持不变
  • 状态更新是不可变的,我们没有直接修改原任务对象,而是创建了一个新对象
  • 可以扩展此功能,添加完成时间记录:{ ...task, completed: !task.completed, completedAt: !task.completed ? new Date() : null }

编辑任务流程

  1. 开始编辑:记录 id 和当前文本
    const startEditing = (id, text) => {setEditingId(id);setEditingText(text);// 可以添加自动聚焦逻辑,提升用户体验
    };
    
  2. 修改内容:更新 editingText 状态
    <inputtype="text"value={editingText}onChange={(e) => setEditingText(e.target.value)}onKeyDown={(e) => {if (e.key === 'Enter') saveEditing(id);if (e.key === 'Escape') cancelEditing();}}
    />
    
  3. 保存:用 map 更新对应任务
    const saveEditing = (id) => {if (!editingText.trim()) {// 可以删除空任务或提示用户return deleteTask(id);}setTasks(tasks.map(task => task.id === id ? { ...task, text: editingText.trim() } : task));setEditingId(null);
    };
    
  4. 取消:清空编辑状态
    const cancelEditing = () => {setEditingId(null);setEditingText("");
    };
    

🎨 组件结构理解

Todo组件
├── 输入区域 (状态: taskInput)
├── 任务列表 (状态: tasks)
│   ├── 任务项
│   │   ├── 任务内容 (条件渲染:编辑模式/显示模式)
│   │   └── 操作按钮 (条件渲染:编辑按钮组/正常按钮组)
└── 统计区域 (计算属性)

补充细节

  • 可以进一步拆分组件,提高复用性和可维护性:
    • TaskInput:负责任务输入和添加
    • TaskList:负责任务列表展示
    • TaskItem:负责单个任务项的渲染和操作
    • TaskStats:负责统计信息展示
  • 组件拆分原则:单一职责原则,一个组件只做一件事
  • 父子组件通信通过 props 传递数据和回调函数

💡 需要深入理解的点

1. 为什么用函数更新状态?

// 可能有问题(闭包)
setTasks(tasks.filter(...));// 更安全(函数式更新)
setTasks(prevTasks => prevTasks.filter(...));

补充细节

  • 当状态更新依赖于前一个状态时,必须使用函数式更新
  • 原因是 React 状态更新是异步的,多个连续更新可能会被合并
  • 函数式更新确保我们总是基于最新的状态进行操作
  • 对于复杂的状态依赖,函数式更新可以避免逻辑错误
  • 示例:连续添加两个任务,使用函数式更新能确保第二个任务基于已添加第一个任务后的数组

2. key 属性的重要性

{tasks.map(task => (<div key={task.id}>...</div>
))}
  • 帮助 React 识别元素
  • 提高渲染性能
  • 必须唯一且稳定补充细节
  • key 只需要在兄弟元素之间唯一,不需要全局唯一
  • 避免使用数组索引作为 key,尤其是在数组会发生增删改的情况下,这会导致 React 错误地复用元素
  • key 不会传递给组件内部,不能通过 props 访问
  • 正确使用 key 可以避免不必要的 DOM 操作,提高性能
  • 当 key 发生变化时,React 会销毁旧元素并创建新元素,这在需要重置组件状态时很有用

3. 事件处理中的箭头函数

// 每次渲染都创建新函数
<button onClick={() => handleClick(id)}>// 性能更好的方式(需要useCallback)
<button onClick={handleClick}>

补充细节

  • 内联箭头函数在每次渲染时都会创建一个新函数,可能导致子组件不必要的重渲染
  • 使用 useCallback 可以缓存函数引用,避免不必要的函数创建:
    const handleClick = useCallback((id) => {// 处理逻辑
    }, []); // 依赖项数组为空时,函数只会创建一次
    
  • 对于简单组件,性能差异可以忽略不计,优先考虑代码可读性
  • 传递参数的另一种方式:使用数据属性(data-*)存储参数,在事件处理函数中通过 e.target.dataset 获取

🚀 下一步学习方向

短期巩固

  1. 重新手写一遍代码(不看现成代码)
  2. 添加输入验证(如任务长度限制)
  3. 添加本地存储(localStorage)
    // 保存到本地存储
    useEffect(() => {localStorage.setItem('tasks', JSON.stringify(tasks));
    }, [tasks]);// 从本地存储加载
    const [tasks, setTasks] = useState(() => {const saved = localStorage.getItem('tasks');return saved ? JSON.parse(saved) : [];
    });
    

中长期进阶

  1. useEffect - 副作用处理
  2. useContext - 全局状态管理
  3. 自定义 Hooks - 逻辑复用
    // 示例:自定义useLocalStorage hook
    function useLocalStorage(key, initialValue) {const [value, setValue] = useState(() => {const saved = localStorage.getItem(key);return saved ? JSON.parse(saved) : initialValue;});useEffect(() => {localStorage.setItem(key, JSON.stringify(value));}, [key, value]);return [value, setValue];
    }
    
  4. 性能优化 - React.memo, useCallback

📝 自我检查问题

  •  我能独立写出添加任务的逻辑吗?
  •  我理解 map 和 filter 在状态更新中的用法吗?
  •  我知道条件渲染的几种写法吗?
  •  我能解释为什么状态更新要用不可变方式吗?

💪 学习心得

从 "完全不会" 到 "基本实现",证明了:

  1. 分解问题:大功能拆成小步骤
  2. 逐步实现:一次只解决一个问题
  3. 调试技巧:console.log + 浏览器工具
  4. 不怕犯错:每个错误都是学习机会

记住:理解比记忆更重要,多实践才能真掌握!


下次复习时,尝试不看代码重新实现,看看哪些概念已经内化,哪些还需要加强。

http://www.dtcms.com/a/597342.html

相关文章:

  • 网站性能优化方案网络设计及网络设计文档
  • 香港科技大学广州|可持续能源与环境学域博士招生宣讲会—兰州大学专场
  • 下午察:当机器人变得太像人
  • 青海城乡与建设厅网站个人简历简短范文
  • 黑马JAVAWeb -Vue工程化-API风格 - 组合式API
  • ubuntu更新nvidia显卡驱动
  • React Native 自建 JS Bundle OTA 更新系统:从零到一的完整实现与踩坑记录
  • 珠海建设网站公司代刷网站只做软件下载
  • 磐安县建设局网站甘肃营销型网站制作
  • UEC++ 如何知道有哪些UComponent?
  • 创建轻量级 3D 资产 - Three.js 中的 GLTF 案例
  • Android 主线程性能优化实战:从 90% 降至 13%
  • EPLAN电气设计-EPLAN在翻译中遇到的问题解析
  • 了解正向代理服务器:功能与用途
  • 建设厅网站业绩备案公示期诸城网络推广公司
  • sendfile函数与传统 read+write 拷贝相比的优势
  • ARL部署
  • 突破智能体训练瓶颈:DreamGym如何通过经验合成实现可扩展的强化学习?
  • 如何学习销售技巧,提高销售能力?
  • 建设北京公司网站兰州网站建设方案
  • 乐趣做网站公众信息服务平台
  • 有源代码怎么制作网站企业网络营销推广方案策划
  • C#使用Chart图表控件实时显示运动坐标
  • 数据结构---哈夫曼树的实现
  • 扁平 网站 模板物联网网站开发公司
  • 新增网站建设方案六安网站建设六安
  • DeepSeek-OCR——上下文视觉压缩:同等长度下,通过更少的视觉token解决长上下文处理难题
  • 从同步耦合到异步解耦:消息中间件如何重塑系统间的通信范式?
  • AI: n8n工作流自动化
  • 上市公司数字化转型策略数据(2000-2024)