一网打尽react手写题(上)

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 检测状态变化并触发重渲染的关键。

🔄 副作用处理 (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 元素数组。
  • key 属性:
    • <li key={todo.id}>:为列表项提供唯一的标识符。这有助于 React 的 Diff 算法高效地更新 DOM,避免渲染错误。代码中使用 Date.now() 生成唯一 ID 是一种常见的简单策略。

⚛️ 事件处理与交互逻辑

  • 事件绑定: onClick 等标准 React 事件。
  • 内联处理 vs 定义函数:
    • 简单逻辑直接在 JSX 中写箭头函数 onClick={() => setCount(...)}。
    • 复杂逻辑(如添加 Todo)提取为独立函数 addTodo。
  • 逻辑边界处理:
    • 防负数: 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
相关推荐
To_OC10 小时前
从一头雾水到跑通全流程:我用一个周末啃透了JWT登录鉴权
前端·后端·http
excel11 小时前
研究 Vue 3 源码的收获
前端·vue.js
可乐鸡翅yeah_13 小时前
业务中 M3U8 水印相关坑,硬水印和动态水印区别
前端·网络·数据库·ffmpeg·m3u8在线
lerhxx14 小时前
AI 应用如何高效优雅地恢复中断?—— "连接解耦 + 状态持久化"
前端·javascript
计算机魔术师14 小时前
METR 演示 AI 智能体如何篡改 Inspect 评估记录以掩盖不当行为
前端
前端snow14 小时前
ai agent --- 异步处理之 Rabbit MQ
前端
念何架构之路14 小时前
zap扩展生态与总结
java·前端·数据库
独孤九剑打醒他14 小时前
【原创开源】【概念设计】源 - 栅 - 漏 - 栅 - 源 横向双栅 MOS,低压交流多值逻辑芯片探索
前端·其他·架构·开源·硬件工程
mantou13215 小时前
我给 AI Agent 做了个「油猴」:让 Claude Code / Codex 直接用你已登录的浏览器
前端·javascript·后端
默_笙15 小时前
🍕 一个主编、三个工种、两本手册:搭一支 AI 调研队
前端·javascript