受控组件 vs 非受控组件
Controlled component vs uncontrolled component
一句话:受控 = 值由 React state 说了算(value + onChange);非受控 = 值由 DOM 自己保管, 需要时用 ref 去读。
| 受控 | 非受控 | |
|---|---|---|
| 值放哪 | React state | DOM 节点 |
| 怎么读 | 直接读 state | ref.current.value |
| 每次输入重渲染 | 会 | 不会 |
| 实时校验 / 联动 | 容易 | 难 |
| 代码量 | 多 | 少 |
默认用受控。因为一旦要做「输入为空就禁用提交按钮」 「实时显示字数」「两个字段联动」, 受控是唯一顺手的方式 ——Q1 那道真题的校验要求就是这样。
非受控的两个真实场景:<input type="file">(只能非受控,出于安全 JS 不能设它的值); 以及性能敏感的大表单 (每次按键都重渲染整个表单时)。
会追问(高频):「value 传了但没传onChange 会怎样?」—— 输入框变成只读, 打字没反应,React 还会警告。
「怎么给受控组件一个初始值又不锁死?」——defaultValue,但那就是非受控了。
「value={undefined} 呢?」—— React 会把它当成非受控,然后在你后来传了值时报「从非受控变成受控」的警告。 所以初始值该写 "" 而不是undefined。
In one line: controlled = React state owns the value (value + onChange); uncontrolled = the DOM keeps the value and you read it with a ref when you need it.
| Controlled | Uncontrolled | |
|---|---|---|
| Where the value lives | React state | The DOM node |
| How you read it | Straight from state | ref.current.value |
| Re-renders on every keystroke | Yes | No |
| Live validation / linked fields | Easy | Hard |
| Amount of code | More | Less |
Default to controlled. The moment you need “disable submit while the field is empty”, a live character count, or two fields that react to each other, controlled is the only comfortable option — the validation requirement in the real Q1 question works exactly that way.
Two real cases for uncontrolled: <input type="file"> (it can only be uncontrolled — for security reasons JS cannot set its value); and performance-sensitive large forms, where every keystroke would otherwise re-render the whole form.
Follow-ups — these come up a lot: “What happens if you pass value but no onChange?” — the input goes read-only, typing does nothing, and React warns you.
“How do you give a controlled component an initial value without locking it?” — defaultValue, but that makes it uncontrolled.
“And value={undefined}?” — React treats it as uncontrolled, then warns you about switching from uncontrolled to controlled once you do pass a value. So the initial value should be "", not undefined.