受控组件和非受控组件有什么区别?应该在什么场景下选择使用它们?
在 React 开发中,处理表单元素(如 <input>、<textarea> 和 <select>)时,我们总会遇到两种模式:受控组件(Controlled Components) 和 非受控组件(Uncontrolled Components)。这两种模式的核心区别在于数据(state)由谁来管理。
理解它们的差异与适用场景,能帮助我们构建出更可预测、更易维护的应用。
什么是受控组件?
在受控组件中,表单元素的值由 React 的 state 完全控制。我们可以将其理解为,React state 是表单数据的 唯一数据源(Single Source of Truth)。
它的工作流程通常如下:
- 组件的
state中存储着表单元素的当前值。
- 组件的
- 这个
state值通过value(或checkedforcheckboxes/radios)prop传递给表单元素。
- 这个
- 当用户与表单元素交互时(例如输入文字),会触发
onChange事件。
- 当用户与表单元素交互时(例如输入文字),会触发
onChange事件处理器会更新组件的state。
state的更新导致组件重新渲染,表单元素也随之显示出新的值。
示例:一个受控的输入框
import React, { useState } from 'react';
function ControlledForm() {
const [name, setName] = useState('');
const handleChange = (event) => {
// 实时将输入值更新到 state
setName(event.target.value);
};
const handleSubmit = (event) => {
alert('提交的名字是: ' + name);
event.preventDefault();
};
return (
<form onSubmit={handleSubmit}>
<label>
名字:
<input type="text" value={name} onChange={handleChange} />
</label>
<button type="submit">提交</button>
</form>
);
}在上述代码中,<input> 的值完全由 name 这个 state 驱动。用户的任何输入都必须经过 handleChange -> setName -> state 更新 -> 重新渲染 这个循环,才能在界面上生效。
受控组件的优势
即时验证:由于每次输入都会更新
state,我们可以在handleChange中轻松实现实时的数据校验、格式化或字符限制。条件化逻辑:可以根据输入框的值,动态地禁用提交按钮或显示提示信息。
单一数据源:表单状态集中在组件的
state中管理,使得调试和状态追踪变得更加清晰、可预测。
什么是非受控组件?
与受控组件相反,非受控组件的表单数据由 DOM 自身管理,而不是 React state。React 不再直接控制表单元素的值,而是需要时才从 DOM 中“拉取”它。
我们通常使用 ref 来获取对 DOM 节点的直接引用,从而在需要时(例如表单提交时)读取其当前值。
示例:一个非受控的输入框
import React, { useRef } from 'react';
function UncontrolledForm() {
const nameInputRef = useRef(null);
const handleSubmit = (event) => {
// 通过 ref 从 DOM 中直接获取值
alert('提交的名字是: ' + nameInputRef.current.value);
event.preventDefault();
};
return (
<form onSubmit={handleSubmit}>
<label>
名字:
{/* 使用 defaultValue 设置初始值,且没有 value prop */}
<input type="text" defaultValue="Guest" ref={nameInputRef} />
</label>
<button type="submit">提交</button>
</form>
);
}在这里,我们使用 useRef 创建了一个 ref 并附加到 <input> 元素上。请注意,我们使用的是 defaultValue 而不是 value 来设置初始值。React 不会追踪输入框值的变化,只有当 handleSubmit 被调用时,我们才通过 nameInputRef.current.value 去读取 DOM 中的最新值。
非受控组件的优势
代码更简洁:对于简单的表单,无需为每个输入框都编写onChange处理器和useState,代码量更少。更接近原生 HTML:其工作方式与传统HTML表单类似,学习成本较低。集成方便:在与一些非React的、直接操作DOM的库集成时可能更方便。性能:在极少数情况下,如果一个表单包含大量输入元素且实时更新state导致性能问题,非受控组件可以避免每次输入都触发重新渲染。
对比与选择指南
为了更直观地对比,我们可以从以下几个维度来分析:
| 特性 | 受控组件 | 非受控组件 |
|---|---|---|
| 数据流 | React state → UI (单向数据流) | DOM 自身管理 state |
| 值来源 | React state 是唯一的数据源 | DOM 是数据源,通过 ref 读取 |
| 实现方式 | value + onChange + useState | ref + defaultValue |
| 实时交互 | 强大,可轻松实现即时验证、动态 UI | 弱,需要在特定事件(如 submit)中手动获取 |
| 复杂度 | 相对更高,需要更多模板代码 | 更低,代码简洁 |
我们应该如何选择?
首选受控组件 是 React 社区普遍推荐的最佳实践。它更符合 React 声明式编程的理念,通过 state 驱动 UI,让组件的行为更加可预测和易于控制。
在以下场景中,受控组件是理想之选:
需要对用户输入进行实时验证或格式化。
需要根据一个输入框的值,动态地改变另一个 UI 元素(例如,当输入框为空时禁用提交按钮)。
多个表单字段之间存在依赖关系。
在以下特定场景中,可以考虑使用非受控组件:
非常简单的表单:例如一个搜索框或登录表单,我们只关心提交时的最终值。一次性取值:当你只想在特定时刻读取一次值,而不需要追踪它的每一次变化时。处理文件输入:<input type="file" />的value是只读的,因此它本质上就是一个非受控组件。我们必须通过ref来访问其选择的文件。性能瓶颈:在一个包含成百上千个输入元素的极其复杂的表单中,如果受控组件的频繁 state 更新确实引发了性能问题(请务必先进行性能分析),非受控组件可能是一个优化手段。
总结
受控组件和非受控组件是 React 提供的两种处理表单的有效模式,它们没有绝对的优劣之分,而是服务于不同的需求。
受控组件将表单数据纳入
React的state管理体系,提供了强大的控制力和可预测性,是大多数情况下的首选方案。非受控组件则将数据管理权交还给
DOM,实现起来更简单快捷,是在一些简单场景或特定需求下的实用备选。
理解这两种模式的内在机制和各自的权衡,能帮助我们在开发中做出更明智的技术决策,编写出既健壮又易于维护的 React 代码。
