CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
React 核心/课时/表单与受控组件
RawBlame
课时目录14 节

React 核心

  1. 01React 是什么
  2. 02JSX 与渲染
  3. 03函数组件
  4. 04Props 与 State
  5. 05事件处理
  6. 06条件渲染与列表
  7. 07useEffect
  8. 08useRef 与 DOM
  9. 09useMemo、useCallback 与自定义 Hook
  10. 10表单与受控组件
  11. 11Context 共享状态
  12. 12React Router 入门
  13. 13数据获取与错误边界
  14. 14综合练习与 Next.js 方向

表单与受控组件

输入绑定、校验、提交。

30 分钟

表单与受控组件

这一课你将能做到

  1. 写出受控输入:value + onChange
  2. 提交时做空值校验,并在保存中禁用按钮
  3. 理解受控相对「输入框自己握值」的好处

为什么要学

本站「留一句」「登录框」都是表单。
受控组件人话:输入框显示什么,完全由 React state 说了算;每次敲键盘都更新 state。

好处:随时能校验、数还剩多少字、禁用提交、把值交给父组件去调 API。
坏处:每个键都触发渲染——对留言框完全可接受。

对照:非受控是输入框自己握值,React 用 defaultValue 给初值,提交时再去读 DOM。特殊场景才用;本课以受控为主。

先建立图像

键盘输入 → onChange → setState → 重新渲染 → input 的 value 来自 state
单一数据源在 React,不在 DOM

少了 onChange 却写了 value:框会锁死,敲不了字。这是入门第一现场。

跟着做

步骤 1:受控留言(对齐离开页思路)

import { useState } from "react";

export function LeaveNote({ onSave }) {
  const [note, setNote] = useState("");
  const [saving, setSaving] = useState(false);

  async function save() {
    const text = note.trim();
    if (!text || saving) return;
    setSaving(true);
    try {
      await onSave(text); // 父组件去调 API
      setNote("");
    } finally {
      setSaving(false);
    }
  }

  return (
    <div>
      <label>
        留一句
        <textarea
          value={note}
          maxLength={500}
          onChange={(e) => setNote(e.target.value)}
        />
      </label>
      <p>还可写 {500 - note.length} 字</p>
      <button type="button" disabled={!note.trim() || saving} onClick={save}>
        {saving ? "保存中…" : "离开本站"}
      </button>
    </div>
  );
}

对照:trim 后空格不算内容;保存中 disabled 防双份请求;成功后清空。

步骤 2:checkbox 用 checked

function Agree({ on, onChange }) {
  return (
    <label>
      <input
        type="checkbox"
        checked={on}
        onChange={(e) => onChange(e.target.checked)}
      />
      我已阅读
    </label>
  );
}

不要用 value 表示勾选。

步骤 3:多字段(字段少时多个 useState 更直观)

const [name, setName] = useState("");
const [email, setEmail] = useState("");

字段很多再考虑一个对象 + 按 name 更新。入门别先上表单库。

步骤 4:了解非受控

<input defaultValue="初始" />

没有全程 value/onChange。要读值可用 ref。文件上传 通常只能非受控。

<input type="file">

步骤 5:提交防抖与空值

function onSubmit(e) {
  e.preventDefault();
  if (!title.trim()) {
    setError("标题必填");
    return;
  }
  setError(null);
  // 再提交
}

讲清楚要点

  1. 受控必须同时有 value 与 onChange,否则输入锁死。
  2. checkbox 用 checked + onChange。
  3. 提交中 disable,防止连点双份请求。
  4. trim 后判断空,只空格不算有效内容。
  5. 错误信息也是 state(或提交失败时的字符串),不要只 console.error。

你可能会懵的地方

  • 光标乱跳?
    常见于边输入边把 value 改成别的格式(每键强制大写、每键 trim)。先做简单受控,格式化放到 blur 或提交。

  • 中文输入法组字
    一般 onChange 即可;极个别要处理 composition 事件。入门先不管。

  • 父要实时知道输入?
    受控可把 state 放父,或 onChange 往上抛。这就是「提升」。

  • 为何 label 要包住输入?
    点文字也能聚焦,无障碍更好。htmlFor + id 是另一种写法。

  • 受控 vs 非受控?
    受控:value+onChange。非受控:默认值 + ref 读取。表单校验常见受控。

错法对照

错法问题正解
只有 value 无 onChange敲不了字补 onChange
保存中仍可点重复提交saving 锁
用 innerHTML 显示用户留言XSS当文本渲染
每键 trim 写回空格打不进去提交时 trim
只校验 UI 不校验 API可绕过后端仍要校验
value 却不写 onChange输不进成对绑定

自测三问

  1. 什么叫受控组件?缺 onChange 会怎样?
  2. 为什么保存中要 disabled?
  3. 空格为什么通常要 trim?checkbox 用哪个属性?

小结一句话

受控表单让 state 成为唯一数据源;校验、禁用、提交都围绕这份 state 转。

动手加深

做登录框:用户名受控、错误文案、submit preventDefault;空用户名不能发请求。

跟着做(加厚 · react-forms)

步骤:最小可运行对照

在 Vite 或 Next 练习页粘贴本课核心示例,改一个输入,确认屏幕变化符合预期。再故意写一个错法表里的写法,看控制台/界面如何坏,然后改回。

每个概念都问:state 在谁手里?UI 哪一块会因它重绘?

操作顺序:打开对应文件 → 改一处可观察点(文案、类型、className 或分支)→ 保存看结果 → 复原或留下有意义的小改动。

步骤:错法故意练一次

先按「用 jQuery 式直接改 DOM 冒充完成」做 30 秒(或在注释里写出若这样做会怎样),再改成「改数据(state/props)让 UI 推导出来」。 目的不是留坑,而是让失败现象进长期记忆。

你可能会懵的地方(加厚)

  • 热更新后状态丢了?
    正常:整树重挂会重置 state。用 key 或外提状态保留;开发期别被吓到。

  • 和文档示例不一致?
    以你锁的 React 大版本 + react.dev 为准;旧博客的 class 组件示例仅作历史阅读。

  • 这课和前后课怎么接?
    回看大纲上一课的小结一句、下一课标题;用自己的话写「本课产出什么可观察结果」。写不出来就重做跟着做。

错法对照(加厚)

错法问题正解
用 jQuery 式直接改 DOM 冒充完成假完成/埋雷改数据(state/props)让 UI 推导出来
只看不敲第二天全忘至少跑通本课步骤
跳过自测三问以为会了闭卷答再对稿

延伸

  • 对照本站离开页输入思路;react.dev Forms
  • 下一课:Context——跨很多层传数据。
加载本节练习…
← 上一课useMemo、useCallback 与自定义 Hook下一课 →Context 共享状态