大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
表单与受控组件
这一课你将能做到
- 写出受控输入:
value+onChange - 提交时做空值校验,并在保存中禁用按钮
- 理解受控相对「输入框自己握值」的好处
为什么要学
本站「留一句」「登录框」都是表单。
受控组件人话:输入框显示什么,完全由 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);
// 再提交
}
讲清楚要点
- 受控必须同时有 value 与 onChange,否则输入锁死。
- checkbox 用
checked+onChange。 - 提交中 disable,防止连点双份请求。
- trim 后判断空,只空格不算有效内容。
- 错误信息也是 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 | 输不进 | 成对绑定 |
自测三问
- 什么叫受控组件?缺 onChange 会怎样?
- 为什么保存中要 disabled?
- 空格为什么通常要 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——跨很多层传数据。