在 React 中,受控组件(Controlled Components) 和 非受控组件(Uncontrolled Components) 是处理表单元素的两种不同方式,它们的核心区别在于 数据管理的方式 和 与 React 的交互模式。
受控组件(Controlled Components)
定义
表单元素(如 <input>
、<textarea>
、<select>
)的值由 React
的 state
完全控制。用户输入会触发 React
的状态更新,表单的当前值始终与 React
的 state
同步。
关键特征
- 数据流:单向绑定(从 React state 到 DOM)。
- 更新方式:通过 onChange 事件手动更新 state。
- 值的来源:组件的 value 或 checked 属性直接绑定到 state。
代码示例
jsx
import { useState } from 'react';
function ControlledForm() {
const [inputValue, setInputValue] = useState('');
const handleChange = (e) => {
setInputValue(e.target.value); // 手动同步到 state
};
return (
<input
type="text"
value={inputValue} // 值由 React state 控制
onChange={handleChange}
/>
);
}
适用场景
- 需要实时验证输入(如密码强度检查)。
- 动态表单(如根据输入内容显示其他字段)。
- 强制输入格式(如只能输入数字)。
优点
- 完全控制表单数据,确保 React state 是唯一数据源。
- 支持复杂的交互逻辑(如输入时实时反馈)。
缺点
- 代码量较多(需要为每个表单元素编写事件处理函数)。
- 频繁的 state 更新可能导致性能问题(大型表单需优化)。
非受控组件(Uncontrolled Components)
定义
表单元素的值由 DOM
自身管理,React
通过 ref
在需要时(如表单提交时)直接读取 DOM
的值。用户输入不会触发 React
的状态更新。
关键特征
- 数据流:直接操作 DOM。
- 更新方式:通过 ref 手动获取值。
- 值的来源:DOM 节点的当前值。
代码示例
jsx
import { useRef } from 'react';
function UncontrolledForm() {
const inputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
console.log(inputRef.current.value); // 通过 ref 获取 DOM 值
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
ref={inputRef} // 使用 ref 关联 DOM 节点
defaultValue="初始值" // 仅初始化时设置默认值
/>
<button type="submit">提交</button>
</form>
);
}
适用场景
- 简单表单(不需要实时验证或动态交互)。
- 文件上传(
<input type="file">
必须用非受控组件)。 - 性能敏感场景(避免频繁 state 更新)。
优点
- 代码简洁,无需管理状态。
- 性能更好(减少渲染次数)。
缺点
- 无法实时控制数据(如强制输入格式)。
- 不符合 React 的"单一数据源"原则。
核心区别对比
特性 | 受控组件 | 非受控组件 |
---|---|---|
数据管理 | React state 控制 | DOM 自身管理 |
值同步 | 实时同步(onChange 事件) | 手动获取(通过 ref) |
初始值设置 | 通过 value 属性 | 通过 defaultValue 属性 |
表单验证时机 | 输入时实时验证 | 提交时验证 |
代码复杂度 | 较高(需处理事件和状态) | 较低(直接操作 DOM) |
性能 | 可能较低(频繁渲染) | 较高 |
React 哲学匹配度 | 高(符合单向数据流) | 低(依赖 DOM 操作) |
如何选择
- 优先受控组件:
大多数场景推荐使用受控组件,尤其是需要实时交互、验证或动态表单时。
- 非受控组件的合理场景:
- 文件上传(
<input type="file">
)。 - 性能敏感且无需实时反馈的表单。
- 与非 React 的第三方库集成(如富文本编辑器)。
注意点
- 文件输入:
<input type="file">
必须用非受控组件,因为其值只能由用户设置(安全限制)。
- 默认值:
非受控组件用 defaultValue
或 defaultChecked
设置初始值(类似原生 HTML
)。
- 受控组件的性能优化:
大型表单中避免频繁渲染,可通过防抖(debounce
)或 useCallback
优化事件处理函数。
总结
- 受控组件:数据由 React 完全控制,适合复杂交互。
- 非受控组件:数据由 DOM 管理,适合简单场景或性能敏感需求。
根据实际需求选择,优先遵循 React
的单向数据流原则,仅在必要时使用非受控组件。