本节摘要:React 用驼峰事件属性做声明式绑定,用户输入则通过"受控组件"桥接进 state。理解受控与非受控的取舍,再处理好几类常用输入(文本、勾选、单选、下拉),表单就顺手了。本节把事件与表单一起串完。
本节阅读目标
阅读完本节,你应当能够:
React 不要求你手动 addEventListener,而是用驼峰属性直接绑定:
function Button() { const handleClick = (e) => { e.preventDefault(); // 需要时阻止默认行为 console.log('点了', e.target); }; return <button onClick={handleClick}>点击</button>; }
事件对象 e 由 React 统一包装,用法与原生事件基本一致。命名上:onClick、onChange、onSubmit 等规则一目了然,这与 Vue 的 v-on 是两套风格的分界(第 4.6 节会对比)。
受控组件的精髓:输入框的值绑定 state,值一变就回写 state,state 一变又刷新输入框。两路绑定由 state 做唯一数据源:
function LoginForm() { const [name, setName] = useState(''); const submit = (e) => { e.preventDefault(); console.log('提交', name); }; return ( <form onSubmit={submit}> <input value={name} onChange={(e) => setName(e.target.value)} /> <button type="submit">登录</button> </form> ); }
value 绑定 name,onChange 把输入写回 name,下值可即时格式化、校验,也方便受控——这正是"界面是数据的函数"在表单维度的写照。
| 输入类型 | 绑定方式 | 说明 |
|---|---|---|
| 文本输入框 | value + onChange | 用 target.value 回写 |
| 复选框 | checked + onChange | 注意用 checked 而非 value |
| 单选组 | checked 按值比对 | 同一组用同一 state 判中 |
| 下拉选择 | value + onChange | 用 select 的 value |
处理一段综合示例:
function Settings() { const [pref, setPref] = useState({ theme: 'light', agree: false }); const set = (key) => (e) => setPref({ ...pref, [key]: key === 'agree' ? e.target.checked : e.target.value }); return ( <form> <select value={pref.theme} onChange={set('theme')}> <option value="light">亮色</option> <option value="dark">暗色</option> </select> <label> <input type="checkbox" checked={pref.agree} onChange={set('agree')} /> 同意协议 </label> </form> ); }
勾选用 checked、其余用 value,是这几类输入最常出错的一处分界。
非受控组件让 DOM 自己存状态,靠 ref 在需要时取值,少一次两路绑定。适合:纯一次性取值、无需实时校验的表单。但一旦"输入要影响别的 UI"或"需要受控回写",就回到受控。默认先受控,非受控是例外。
顺带说一句受控流程带来的另一个好处:因为唯一数据源是 state,做"输入格式化"就只是"在 onChange 里改一下再写回",做"校验提示"也只是读 state 的派生值。这两件事在非受控写法里都得额外翻 DOM,繁琐得多。所以只要你的表单有任何联动、校验或格式化需求,受控都是更短的路——这不是偏好,而是"数据进 state"后自然衍生的便利。

多数受控表单字段的 onChange 长得一模一样:"收到事件 → 把 key 对应的值写回 state"。重复到第二个字段就值得抽一个小工具,让每个输入框都只声明"我是哪个 key":
function Account() { const [form, setForm] = useState({ email: '', age: '' }); const bind = (key) => (e) => setForm((f) => ({ ...f, [key]: e.target.value })); return ( <> <input value={form.email} onChange={bind('email')} placeholder="邮箱" /> <input value={form.age} onChange={bind('age')} placeholder="年龄" /> </> ); }
bind(key) 返回的每个事件处理器都知道自己该更新哪个字段,十几个字段也只是一排 bind('xxx'),可读性不掉、改动点集中。请看它怎么处理"更新依赖旧值"这一茬:回调里用的是 setForm((f) => ...) 的函数式更新,而不是直接读闭包里的 form——这一步避免了连续多次快速输入时读到过期值的问题。这个"传 key 的函数返回处理器"的套路,是受控表单里最有泛化价值的一种,你在 4.6 节 Vue 的双向绑定里会看到它被框架直接内建成了 v-model,原理正是同一条"key 属性写回 state"的缩写。
React 的内容在此收束。从环境搭建到事件表单,你已经能在 React 里写一个自作完整的小应用。下一章我们用同一套思想跨到 Vue——你会发现组件、单向数据流、响应式如此熟悉,只是语法换了一批。