← 返回题目列表

React 受控组件和非受控组件有什么区别?

高频 中等 第 2 / 27 题 更新于 2026/07/27
React表单受控组件非受控组件

简化版

受控组件的表单值由 React state 控制,输入变化通过 onChange 更新 state;非受控组件的值由 DOM 自己维护,React 通过 ref 在需要时读取。受控组件更可控,非受控组件更轻量。

详细版

受控:

const [value, setValue] = useState('');
<input value={value} onChange={e => setValue(e.target.value)} />

非受控:

const ref = useRef(null);
<input ref={ref} defaultValue="Tom" />

受控适合实时校验、联动、格式化、提交前状态统一管理。非受控适合简单表单、文件上传、第三方 DOM 插件或不需要实时同步的场景。

完整版教学

一、受控组件把 DOM 值交给 React

受控组件中,输入框显示什么由 state 决定。用户输入触发 onChange,React 更新 state,再重新渲染 input。

这种方式让数据流清晰:UI 是 state 的映射,表单值随时在 React 中可见。

二、非受控组件让 DOM 自己维护值

非受控组件使用 defaultValue 初始化,后续输入变化不每次同步到 React state。需要提交时,通过 ref 读取 DOM 当前值。

文件上传 <input type="file"> 通常就是非受控,因为文件值不能像普通文本一样由 React 设置。

function handleSubmit(e) {
  e.preventDefault();
  const data = new FormData(e.currentTarget);
  console.log(data.get('email'));
}

非受控表单不等于必须逐个 ref 读取,原生 FormData 可以按 name 一次取得提交快照。代价是提交前 React 不天然拥有每个字段的实时值,跨字段联动仍需监听或引入表单状态层。

重置时也要分清契约:受控字段重置 state,非受控字段可调用表单 reset 或重新挂载,但不能混用两套来源竞争同一个当前值。

三、两者的取舍

受控组件更适合复杂表单:字段联动、实时校验、按钮禁用、格式化输入、错误提示。缺点是每次输入都触发状态更新,超大表单需要优化。

非受控组件更简单,减少 React 状态更新,但状态不集中,实时联动较麻烦。

四、面试追问与工程落地

常见追问是“为什么 input 从 undefined 变成字符串会警告”。这是 React 检测到组件从非受控变成受控。应该给受控 input 明确初始值,比如空字符串,而不是一开始 undefined。

工程里表单库也在做权衡。Formik 偏受控思路,React Hook Form 大量利用非受控和 ref 降低重渲染。选择不是绝对,要看表单复杂度和性能要求。

五、控制权由 value/checked 契约决定

文本输入传 value 后就是受控组件,复选框和单选框则由 checked 控制;对应的 onChange 必须同步把 DOM 提供的新值写回 state。若只传 value 却不更新它,React 每次提交都会把输入框恢复成旧值,用户表现上就像无法输入。

<input value={name} onChange={e => setName(e.target.value)} />
<input type="checkbox" checked={agreed}
       onChange={e => setAgreed(e.target.checked)} />

defaultValuedefaultChecked 只定义初始值,挂载后修改这些 props 不负责覆盖用户已经输入的当前值。文件选择框的文件列表由浏览器和用户操作控制,通常通过 ref 或表单提交读取,不能像文本值一样用 React state 指定任意本地文件路径。

维度受控组件非受控组件
当前值来源React state/propsDOM 内部状态
初始化value / checkeddefaultValue / defaultChecked
读取时机render 中随时可用提交时用 ref/FormData
实时校验与联动直接需要额外监听或提交后处理
每次输入的 React 更新通常有可以没有

六、用更新范围衡量大型表单成本

假设表单有 200 个字段,每敲一个字符都把整个表单对象放到顶层 state;若所有字段组件都随父级执行,一次按键可能带来约 200 个字段的 render 检查。把字段状态下沉、按字段订阅,或采用以非受控注册为主的表单库,可以把更新范围缩小,但要承担校验、默认值和提交数据同步的设计成本。

受控不等于一定慢,关键是一次状态更新波及多少组件以及每个组件渲染多重。应使用 Profiler 测量输入延迟,再决定拆组件、延迟昂贵派生计算或改用非受控;不要为了“少一次 render”牺牲必需的即时业务约束。

选择锚点:需要每个时刻都由 React 决定显示值,就受控;只需要初始化并在提交时读取,就可以非受控。

七、常见误区与追问

  • 误区:传了 onChange 的输入框就是受控组件。 是否受控取决于是否传入 valuechecked,onChange 本身不能定义控制权。
  • 误区:修改 defaultValue 会持续覆盖输入框。 它只提供初始值,挂载后的当前值仍由 DOM 维护。
  • 误区:非受控组件完全不受 React 管理。 React 仍创建和卸载节点,只是表单当前值不由 React state 驱动。
  • 追问:为什么 value 从 undefined 变成字符串会警告? 组件生命周期中从非受控切到受控会造成契约不一致,文本值应从 '' 开始。
  • 追问:checkbox 为什么读取 checked 而不是 value? value 是提交值,勾选状态由布尔型 checked 表示。
  • 追问:文件输入为什么通常非受控? 浏览器出于安全模型只允许用户选择文件,应用不能用普通 value prop 指定本地文件。
  • 追问:大型表单一定要改成非受控吗? 不一定,应先测更新范围;字段拆分、局部状态和延迟计算也能保留受控模型。

八、加强记忆

受控组件是“值在 React state”,非受控组件是“值在 DOM”。要实时联动和校验用受控,要轻量和少渲染可考虑非受控。

同时记住文本看 value、勾选看 checked,控制方式在组件生命周期内不能来回切换。