
一句话区别
受控:你用 state 决定输入框现在显示什么。非受控:输入框自己管自己的值,你只在需要时去读。
受控:value 加 onChange
function Form() { const [firstName, setFirstName] = useState('')
return ( <input value={firstName} onChange={e => setFirstName(e.target.value)} /> )}官方规则很硬:传了 value(复选框、单选框则传 checked),就必须同时给 onChange。缺了它,输入框会变成只读,控制台还会报一条错,因为你等于每次都把它强行设回同一个值。
什么时候值得受控:你要在每次输入时更新界面的其他部分(实时预览、字符计数、格式校验),或者除输入框之外还有别的途径改这个值,比如旁边一个「+10 岁」按钮。
非受控:defaultValue 加提交时读取
function Form() { function handleSubmit(e) { e.preventDefault() const formData = new FormData(e.target) console.log(Object.fromEntries(formData.entries())) }
return ( <form onSubmit={handleSubmit}> <input name="firstName" defaultValue="Taylor" /> <input name="age" type="number" /> <button type="submit">提交</button> </form> )}两个要点。初始值用 defaultValue(复选框用 defaultChecked),不要写成 value。每个 <input> 都要有 name,FormData 拿它当键。
只在提交时取一次数据的表单适合这种写法:字段再多也不用为每个字段建 state。
三条容易踩的规则
两种模式不能混,也不能中途切换。value 从 undefined 变成字符串时,React 会报「A component is changing an uncontrolled input to be controlled」。初值给空字符串 '',不要给 null 或 undefined。
复选框读 e.target.checked,不是 e.target.value。
<form> 里那个不带 type 的 <button> 会提交表单。自定义按钮组件记得写 type="button",否则它会意外提交。
输入时的重渲染怎么控
受控输入每敲一个键都会更新 state,装着这个 state 的组件就会重渲染。把输入框的状态关进它自己的小组件,别让整页跟着动:
function App() { return ( <> <SignupForm /> <PageContent /> </> )}PageContent 不依赖输入值,把它挪到状态外面,敲键时就只有 SignupForm 重渲染。
如果别的部分确实要用这个值(搜索框与结果列表),用 useDeferredValue 让输入保持跟手。
顺带把标签关联上
每个输入框都该有标签。把 <input> 包进 <label> 最省事;两者分开写时,用 htmlFor 配 id,id 交给 useId 生成,避免同一组件渲染多次时撞车。
文件输入只能非受控
<input type="file" /> 的值由浏览器管理,不能用 state 设定,只能读:
function Upload() { function handleSubmit(e) { e.preventDefault() const file = new FormData(e.target).get('avatar') console.log(file) }
return ( <form onSubmit={handleSubmit}> <input type="file" name="avatar" /> <button type="submit">上传</button> </form> )}怎么选
需要实时反应、或有多条改值途径,就用受控。只在提交时取一次,就用非受控。
React 19 还给了第三条路:<form action={fn}> 把 FormData 直接交给动作函数,提交成功后表单自动重置。细节见 React 的表单 Actions。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 189 天
部分内容可能已过时