串联组件、状态、表单、Effect、自定义 Hook 与测试

第十二章:综合项目:任务管理小应用

构建任务管理项目,串联组件拆分、表单、Effect、自定义 Hook 和测试。

本章属于 React 教程的核心路线。建议先通读概念模型,再手写最小示例,最后用练习验证自己能否独立复现。

本章目标

本章用任务管理小应用收束整套 React 教程:Task、TaskForm、TaskList、localStorage、组件拆分、表单、Effect、自定义 Hook 与测试。

阅读本章时,请把每个 API 放回“数据如何变成界面、用户操作如何改变数据”的主线中理解。React 的学习重点不是背诵语法,而是建立可预测的渲染模型。

概念模型

综合项目的关键不是功能堆叠,而是把状态放在合适层级:App 管理任务数组,TaskForm 负责新增输入,TaskList 负责展示和事件回传。

你可以把 React 程序看成一棵由组件组成的元素树。每次输入数据、状态或外部系统发生变化,React 会重新计算需要展示的 React element,再用高效的方式更新浏览器中的 DOM。

最小示例

下面示例展示组件拆分后的核心结构。完成后可以继续阅读 Next.js 教程,把客户端应用扩展到路由和服务端渲染场景。

type Task = { id: string; title: string; done: boolean };

function TaskForm({ onAdd }: { onAdd: (title: string) => void }) {
  const [title, setTitle] = useState("");
  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        onAdd(title);
        setTitle("");
      }}
    >
      <input value={title} onChange={(event) => setTitle(event.target.value)} />
      <button type="submit">添加</button>
    </form>
  );
}

function TaskList({ tasks }: { tasks: Task[] }) {
  return <ul>{tasks.map((task) => <li key={task.id}>{task.title}</li>)}</ul>;
}

export function TaskApp() {
  const [tasks, setTasks] = useLocalStorage<Task[]>("tasks", []);
  return (
    <>
      <TaskForm onAdd={(title) => setTasks([...tasks, { id: crypto.randomUUID(), title, done: false }])} />
      <TaskList tasks={tasks} />
    </>
  );
}
type LessonNote = { title: string; done: boolean };\nconst note: LessonNote = { title: 'React 教程', done: false };

规则与陷阱

规则的价值在于让多人维护同一个项目时仍能预测行为。下面这些限制看似细碎,实际都在保护组件的输入、输出和生命周期边界。

练习

练习应当先小后大:先验证一个概念,再把它接入完整页面。不要在没有理解失败原因时复制更复杂的代码。

  1. 实现 Task、TaskForm、TaskList 三个单元。
  2. 用 localStorage 保存任务列表。
  3. 为新增任务和切换完成状态写一条测试。