1. Counter 计数器
推导链
Q1: 需求是什么?
→ 加、减、清空 + input 联动 + 不能为负数
Q2: 核心是什么?
→ 受控组件:value 绑定 state,onChange 更新 state
Q3: 边界怎么处理?
→ Math.max(0, val) 防负数
→ || 0 防 NaN
javascript
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0)
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = parseInt(e.target.value) || 0
setCount(Math.max(value, 0))
}
return (
<div className="max-w-md mx-auto p-6 flex items-center gap-2">
{/* flex + gap:横向排列 + 间距 */}
<input
type="number"
value={count}
onChange={handleChange}
className="w-20 px-2 py-1 border rounded text-center"
/>
{/* w-20:固定宽度,text-center:数字居中 */}
<button
onClick={() => setCount(c => c + 1)}
className="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600 active:scale-95"
>
+
</button>
{/* active:scale-95:点击时缩小,有反馈感 */}
<button
onClick={() => setCount(c => Math.max(c - 1, 0))}
className="px-3 py-1 bg-gray-200 rounded hover:bg-gray-300 active:scale-95"
>
-
</button>
<button
onClick={() => setCount(0)}
className="px-3 py-1 text-red-500 hover:text-red-600"
>
重置
</button>
{/* 文字按钮,不需要背景 */}
</div>
)
}
2. TodoList
推导链
Q1: 需求?
→ 增删改查 + 完成状态切换
Q2: 数据结构?
→ { id, text, completed }[]
Q3: 核心操作?
→ 增:push 新项
→ 删:filter 过滤
→ 改:map 找到对应 id 修改
→ 切换:map 翻转 completed
javascript
import { useState } from 'react'
interface Todo {
id: number
text: string
completed: boolean
}
export default function TodoList() {
const [todos, setTodos] = useState<Todo[]>([])
const [input, setInput] = useState('')
const addTodo = () => {
if (!input.trim()) return
setTodos([...todos, { id: Date.now(), text: input, completed: false }])
setInput('')
}
const deleteTodo = (id: number) => {
setTodos(todos.filter(t => t.id !== id))
}
const toggleTodo = (id: number) => {
setTodos(todos.map(t => t.id === id ? { ...t, completed: !t.completed } : t))
}
return (
<div>
<input value={input} onChange={e => setInput(e.target.value)} />
<button onClick={addTodo}>添加</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<span
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
onClick={() => toggleTodo(todo.id)}
>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>删除</button>
</li>
))}
</ul>
</div>
)
}
3. CountDown 倒计时
推导链
Q1: 需求?
→ 显示剩余时间,每秒更新,到 0 停止
Q2: 核心?
→ useEffect + setInterval
→ 清理:return clearInterval
Q3: 注意点?
→ 依赖数组要包含 count,或用函数式更新
→ count <= 0 时 clearInterval
javascript
import { useState, useEffect } from 'react'
export default function CountDown({ initial = 10 }: { initial?: number }) {
const [count, setCount] = useState(initial)
useEffect(() => {
if (count <= 0) return
const timer = setTimeout(() => setCount(c => c - 1), 1000)
return () => clearTimeout(timer)
}, [count])
const reset = () => setCount(initial)
return (
<div className='min-h-screen flex items-center justify-center'>
<div className='flex items-center gap-2 p-6'>
<span className='w-20 px-2 py-1 border rounded-e text-center'>{count}</span>
<button className='px-3 py-1 bg-blue-500 rounded hover:bg-blue-600 active:scale-95'
onClick={reset}
>reset</button>
</div>
</div>
)
}
这三个例子(Counter、TodoList、CountDown)它们从简单到复杂,完整覆盖了 React 开发中最核心的几个概念。通过分析代码,我们可以总结出以下 React 关键知识点:
🧠 核心状态管理 (State Management)
这是 React 的基石,主要体现在 useState 的使用上。
- 状态声明与初始化:
- 使用
useState定义组件内部的状态。 - 类型安全: 在 TypeScript 环境下,明确指定了状态类型,如
useState<Todo[]>([])和useState(0)。
- 使用
- 状态更新模式:
- 直接更新: 当新状态不依赖于旧状态时(如重置
setCount(0)或更新输入框setInput),直接传入新值。 - 函数式更新: 当新状态依赖于旧状态时(如
setCount(c => c + 1)或倒计时递减),使用回调函数形式。这确保了在异步更新或批量更新场景下,获取到的是最新的 state 值。
- 直接更新: 当新状态不依赖于旧状态时(如重置
- 不可变性原则:
- 在 TodoList 中,没有直接修改数组(如
push或splice),而是使用扩展运算符[...todos, newItem]或filter、map来创建新的数组引用。这是 React 检测状态变化并触发重渲染的关键。
- 在 TodoList 中,没有直接修改数组(如
🔄 副作用处理 (Side Effects)
主要体现在 CountDown 组件中,用于处理时间、订阅等"副作用"。
useEffect的使用:- 用于在组件渲染后执行副作用逻辑(这里是启动定时器)。
- 依赖数组:
[count]:表示当count变化时,重新执行 Effect。
- 清理机制:
return () => clearTimeout(timer):这是防止内存泄漏的关键。当组件卸载或count变化导致 Effect 重跑时,先清理上一次的定时器。
- 防御性编程:
if (count <= 0) return:在 Effect 内部做逻辑判断,避免不必要的定时器设置。
🎛️ 表单与受控组件 (Forms & Controlled Components)
主要体现在 Counter 和 TodoList 的输入框中。
- 受控组件模式:
<input value={state} onChange={...} />:Input 的值完全由 React 的 state 驱动,而不是 DOM 自身的值。
- 事件处理:
- 处理
onChange事件,从事件对象e.target.value中提取值。 - 类型断言: 在 TS 中使用
React.ChangeEvent<HTMLInputElement>确保类型安全。
- 处理
- 数据清洗:
- 在
handleChange中使用parseInt和|| 0处理空值或非法字符,防止NaN导致程序崩溃。
- 在
📐 列表渲染与键 (Lists & Keys)
主要体现在 TodoList 组件中。
map渲染:- 使用 JavaScript 的
array.map()方法将数据数组转换为 JSX 元素数组。
- 使用 JavaScript 的
key属性:<li key={todo.id}>:为列表项提供唯一的标识符。这有助于 React 的 Diff 算法高效地更新 DOM,避免渲染错误。代码中使用Date.now()生成唯一 ID 是一种常见的简单策略。
⚛️ 事件处理与交互逻辑
- 事件绑定:
onClick等标准 React 事件。 - 内联处理 vs 定义函数:
- 简单逻辑直接在 JSX 中写箭头函数
onClick={() => setCount(...)}。 - 复杂逻辑(如添加 Todo)提取为独立函数
addTodo。
- 简单逻辑直接在 JSX 中写箭头函数
- 逻辑边界处理:
- 防负数:
Math.max(0, val)。 - 空值处理:
if (!input.trim()) return防止添加空任务。
- 防负数:
📌 总结表
表格
| 知识点 | 涉及组件 | 核心代码/概念 |
|---|---|---|
| useState | 全部 | const [count, setCount] = useState(0) |
| 函数式更新 | Counter, CountDown | setCount(c => c + 1) |
| 不可变数据 | TodoList | [...todos, newItem], filter, map |
| useEffect | CountDown | 定时器设置与 clearTimeout 清理 |
| 受控组件 | Counter, TodoList | value={state} + onChange |
| 列表渲染 | TodoList | todos.map(...) + key |
| 类型安全 | 全部 | useState<Todo[]>, React.ChangeEvent |