受控(Controlled)
React 是 single source of truth——值住在 state 里,DOM 只是显示:
const [text, setText] = useState('');
<input value={text} onChange={e => setText(e.target.value)} />
每次按键 → setText → re-render → value={text} 把新值写回 DOM。
优点:
- 随时能读、能改、能校验
- 与其他 state 联动方便
- 可以做受控行为(输入字母自动转大写、限制长度等)
缺点:
- 多打一个字 → 一次 re-render
- 表单字段多(10+)时性能会显示出来
非受控(Uncontrolled)
DOM 是 source of truth——React 不管值,要用时用 ref 取:
const inputRef = useRef<HTMLInputElement>(null);
<input ref={inputRef} defaultValue="" />
const onSubmit = () => {
const val = inputRef.current?.value;
};
注意是 defaultValue 而不是 value——value 一旦传就变受控了。
优点:
- 输入时不触发 React re-render,性能好
- 代码量少
- 与原生 form 行为兼容(reset / submit)
缺点:
- 实时校验麻烦(要监听
onChange自己拿值) - 复杂联动写起来不优雅
怎么选
| 场景 | 推荐 |
|---|---|
| 一两个字段的搜索框 | 受控(简单) |
| 大表单(10+ 字段) | 非受控 + React Hook Form |
| 实时校验 / 联动 | 受控 |
| 富文本 / 复杂编辑器 | 非受控(编辑器自己管状态) |
文件上传 <input type="file" /> |
必须非受控(浏览器安全限制) |
React Hook Form:非受控的现代写法
import { useForm } from 'react-hook-form';
function Login() {
const { register, handleSubmit, formState: { errors } } = useForm();
return (
<form onSubmit={handleSubmit(d => console.log(d))}>
<input {...register('email', { required: true })} />
{errors.email && <span>必填</span>}
<button>提交</button>
</form>
);
}
每个字段 ref 化,不触发 re-render。表单字段越多越香。详见 第 18 篇。
半受控(混合)
const [text, setText] = useState('');
const inputRef = useRef<HTMLInputElement>(null);
<input value={text} onChange={e => setText(e.target.value)} ref={inputRef} />
可以同时拿 React 状态 + 直接操作 DOM(如 focus)。ref 不会让组件从受控变非受控。
切换之坑
<input value={text} /> // text 一开始是 undefined → React 报警 "uncontrolled to controlled"
修法:初值给 '' 而不是 undefined。
const [text, setText] = useState(''); // ✅ 初值是 ''
checkbox / radio / select
<input type="checkbox" checked={x} onChange={...} />——属性叫checked<select value={x} onChange={...}>...——<option selected>不用,统一通过select.value<input type="radio" name="..." value="a" checked={v==='a'} onChange={...} />
文件上传(必须非受控)
<input type="file" onChange={e => {
const file = e.target.files?.[0];
if (file) upload(file);
}} />
不能给 value 设置 file(浏览器禁止——避免你伪造用户选择的文件)。
→ 下一篇 状态管理库选型