在写 React 代码时,你有没有过这样的困惑:
我只是想让用户输入一段文字,然后提交。
为什么非要搞个
useState来管理它?用
ref直接读取 DOM 值不行吗?
如果你也这样想过,那你不是一个人。
今天我们就来聊一聊这个看似简单、实则深藏玄机的问题:React 中的受控组件和非受控组件,到底该选谁?
一、问题的起点:我该如何获取表单值?
最原始的需求是:用户输入内容 → 点击提交 → 获取输入内容。
这听起来很简单,但实现方式却有两条路:
- 方案A :给
<input>加一个value属性,并通过onChange更新状态。 - 方案B :不设置
value,只用ref在提交时读取 DOM 的.value。
我们先看一段代码:
ini
const [value, setValue] = useState('');
const inputRef = useRef(null);
const doLogin = (e) => {
e.preventDefault();
console.log(inputRef.current.value); // 从 DOM 读值
};
return (
<form onSubmit={doLogin}>
<input type="text" value={value} onChange={(e) => setValue(e.target.value)} />
<input type="text" ref={inputRef} />
<button type="submit">登录</button>
</form>
);
这段代码里有两个输入框:
- 第一个是受控的(
value={value}) - 第二个是非受控的(
ref={inputRef})
当你点击"登录",你会看到两个值都被打印出来 ------ 但它们的来源完全不同。
二、思考:为什么需要"控制"?
我们先问自己一个问题:
如果我不用
state控制输入框,而是直接读 DOM,是不是更省事?
看起来是的。代码少,逻辑清晰,还能避免状态同步的问题。
但这里有一个关键的认知偏差:我们以为"读取值"只需要一次操作,但实际上,用户的交互是一个持续的过程。
🌪️ 想象这样一个场景:
你在做一个登录表单,要求密码至少 6 位,且包含数字。
如果使用非受控组件,你只能在提交时判断是否符合规则。
但如果用户输入了 abc123,系统提示"太短了",然后他继续打 456,变成 abc123456,这时候你才发现合格。
问题是:你怎么知道他在中间改了多少次?怎么实时反馈?
而受控组件可以做到这一点:
ini
<input
type="password"
value={form.password}
onChange={(e) => {
const pwd = e.target.value;
setForm({ ...form, password: pwd });
if (pwd.length >= 6 && /\d/.test(pwd)) {
setValid(true);
} else {
setValid(false);
}
}}
/>
这就是"控制"的价值:把数据流从"被动响应"变成"主动驱动"。
三、再深入一步:什么是"受控"?
很多人误以为"受控"就是"加了个 value",其实不然。
真正的"受控"是一种设计理念:
所有的 UI 状态都由 React 的 state 驱动,而不是由 DOM 自行决定。
这意味着:
- 输入框的值来自
state - 用户输入触发事件,更新
state - 页面重新渲染,显示新的值
这是一个闭环,形成了单向数据流。
这种模式的好处在于:
- 数据可预测(不会出现"页面显示 A,实际是 B"的问题)
- 可以在任意时刻进行校验、重置、保存
- 更容易测试和调试
而 ref 虽然能拿到值,但它绕过了 React 的状态系统,属于"黑箱操作"。
四、那什么时候该"放手"?
既然受控这么好,为什么还要有非受控组件?
因为有些场景,我们并不需要"控制"。
比如评论框:
ini
const textareaRef = useRef(null);
const handleSubmit = () => {
const comment = textareaRef.current.value;
if (!comment) {
alert('请输入评论');
return;
}
console.log(comment);
};
在这个例子中:
- 用户输入完就提交
- 不需要实时校验
- 不需要联动其他字段
- 也不需要预览或自动补全
这时候,用 ref 是一种轻量级的选择。
而且,在某些性能敏感的场景下,频繁触发 setState 会影响性能。例如文件上传、富文本编辑器等,这些组件内部有自己的状态管理机制,强行用 React 控制反而会增加复杂度。
五、结论:不是选择题,而是权衡题
回到最初的问题:我应该用受控还是非受控?
答案不是"哪个更好",而是:
根据业务需求做权衡。
| 场景 | 推荐方式 | 理由 |
|---|---|---|
| 登录/注册 | 受控组件 | 需要校验、联动、错误提示 |
| 评论/留言 | 非受控组件 | 一次性提交,无需实时处理 |
| 文件上传 | 非受控组件 | DOM 内部状态复杂,不适合 React 管理 |
| 实时搜索 | 受控组件 | 需要即时反馈结果 |
| 富文本编辑器 | 非受控组件 | 使用第三方库,内部状态独立 |
六、最后的思考:React 的本质是什么?
React 的核心思想是:UI 是状态的函数。
也就是说,页面长什么样,完全取决于当前的状态。
当你使用受控组件时,你是在践行这一理念:每一个变化,都是状态驱动的结果。
而当你使用非受控组件时,你实际上是在说:"这个部分我暂时不想管,让它自己玩。"
这不是坏事,但你要清楚地知道:你在放弃一部分控制权。
所以,不要为了"简洁"而滥用非受控组件,也不要为了"规范"而过度使用受控组件。
我们应该要在"控制"与"放手"之间找到平衡点。
写在最后
技术没有绝对的对错,只有合适的时机。
下次面对一个表单时,可以先想想
"我需要在用户输入的过程中做什么?"
如果答案是"什么也不做",那就放手吧。(useRef)
如果答案是"我要校验、联动、展示",那就牢牢抓住它。(useState)
这才是 React 表单设计的真正智慧。