先记住这个答案
受控输入通过 value 或 checked 接受当前值,用户变化后由事件处理器更新对应的 React 状态;非受控输入由浏览器维护当前值,React 可以提供默认值,并在提交或需要时读取。实时校验、跨字段联动和外部状态必须控制展示时,受控通常更直接;主要在提交时读取、希望利用原生表单行为时,非受控也合适。两者都有状态,只是当前值的权威来源不同。
- 受控由外部输入值驱动,非受控由控件维护当前值
- 实时联动与外部覆盖是重要选择依据
- 性能取决于更新范围与工作成本,不能仅靠模式判断
实时预览与一次性提交需要不同的数据节奏
个人简介编辑器旁边有实时字数和预览时,受控 state 可以直接作为输入框、字数与预览的共同来源。用户每次输入都会更新这份状态,相关界面立即根据同一快照计算。不要再用 Effect 把 DOM 输入复制到另一份 state,制造两套来源。
一个简单留言表单若只在提交时读取内容,也可以让浏览器维护输入,用 FormData 收集数据。此时依然需要 name、标签、错误展示与服务端校验,非受控并不意味着没有数据模型,只是无需为每次按键都建立一份 React 的当前值副本。
外部重置与异步回填要有明确协议
受控表单收到新的业务对象时,可以由状态所有者决定何时替换字段值,并处理尚未保存的编辑。非受控表单的 defaultValue 表达初始默认值,不能充当持续同步外部数据的接口;回填、重置或重新建立表单实例,需要清楚区分是否应该覆盖用户已经输入的内容。
同一张表单也可以使用不同类型的控件,例如文本字段受控、文件选择依赖浏览器 FileList,但每个字段都应只有明确的当前值来源。第三方表单库可能通过注册与订阅管理值,不能只看调用处没有 useState 就断言整个表单没有受控或同步机制。
性能先看更新影响了哪些工作
受控输入每次按键更新状态,但如果状态留在小型编辑区域、渲染轻量,通常不构成问题。真正卡顿可能来自同一父层重复计算大列表、同步校验或渲染复杂预览。可以缩小状态范围、减少计算,或按需要延后昂贵展示,而不是立刻放弃清楚的数据流。
非受控减少 React 对当前输入值的持续管理,也不代表任何操作都更快:读取、校验、联动与错误处理仍有成本。选型应先满足功能和可维护性,再用实际输入与提交场景测量。避免把所有控件改成 ref,却又手工实现复杂的值同步系统。
容易答错的地方
- 非受控组件没有状态
- 浏览器控件仍保存当前值和选择,只是没有由 React 的 value 或 checked 持续控制。理解错这一点,就容易误以为改 defaultValue 能随时覆盖用户输入。
- 受控就必须把全部字段放在页面最顶层
- 状态可以留在局部表单,也可以通过合适的字段订阅组织。提升范围由协调需求决定,把临时输入放得过高可能扩大无关工作,但这不是受控模式的必要条件。
面试官还会怎么问?
只读输入传 value 但不写 onChange 可以吗?
有意只读时应明确提供 readOnly;需要禁用交互时可用 disabled,但它与只读在提交和可操作性上不同。不能把遗漏更新处理器造成的无法输入当作有意设计。
受控值还没加载出来时怎样初始化?
文本字段通常保持字符串,例如空字符串,并用单独加载状态表达数据未就绪;复选框保持布尔值。不要让 undefined 临时切换控件的控制模式。
原生 form reset 会重置受控 state 吗?
React 状态不会仅因浏览器尝试重置控件就自动变成初始业务数据。受控表单应明确处理重置事件或动作,更新权威 state,并定义错误、脏标记与文件选择怎样重置。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。