在 React 中,受控组件 (Controlled Components) 和 非受控组件 (Uncontrolled Components) 是两种管理表单数据的方式,主要区别在于表单数据由谁来管理(状态来源)以及如何获取用户输入的值。
1. 受控组件 (Controlled Components)
核心思想: 表单元素的值(value
)由 React 组件的 state
管理。React 完全控制表单元素的状态。
实现方式:
- 将表单元素(如
<input>
,<textarea>
,<select>
)的value
属性绑定到组件的state
。 - 通过
onChange
事件处理函数监听用户的输入,并在该函数中更新state
。 - 由于
value
由state
控制,任何输入都会先触发onChange
,更新state
,然后state
的变化会重新渲染组件,从而更新value
,形成一个闭环。
优点:
- 数据流清晰: 所有表单数据都集中管理在 React 的
state
中,便于调试和维护。 - 易于验证和转换: 可以在
onChange
处理函数中轻松地对输入进行实时验证、格式化或转换。 - 易于重置或预填充: 只需修改
state
,表单值就会随之改变。 - 符合 React 单向数据流原则。
缺点:
- 代码量稍多: 需要为每个需要控制的表单元素编写
state
和onChange
处理函数。 - 性能开销: 每次输入都触发
state
更新和组件重新渲染(虽然 React 通常能优化得很好)。
示例:
jsx
import React, { useState } from 'react';
function ControlledInput() {
const [name, setName] = useState('');
const handleChange = (event) => {
setName(event.target.value); // 更新 state
};
const handleSubmit = (event) => {
event.preventDefault();
console.log('提交的姓名:', name);
};
return (
<form onSubmit={handleSubmit}>
<label>
姓名:
<input
type="text"
value={name} // value 由 state 控制
onChange={handleChange} // 通过事件更新 state
/>
</label>
<button type="submit">提交</button>
</form>
);
}
2. 非受控组件 (Uncontrolled Components)
核心思想: 表单元素的值由元素自身(DOM)管理,而不是由 React 的 state
管理。React 不直接控制其值。
实现方式:
- 使用
ref
: 通过useRef
Hook 或createRef()
创建一个ref
,并将其附加到表单元素上。 - 获取值: 当需要获取表单数据时(例如在提交时),通过
ref.current
访问 DOM 元素,然后读取其value
属性。 - 默认值: 可以使用
defaultValue
(或defaultChecked
等)属性来设置初始值,之后这个值就由 DOM 自己管理了。
优点:
- 更接近原生 HTML: 对于简单的表单或需要与非 React 代码集成时,使用起来更直接。
- 减少代码量: 对于不需要实时响应输入的场景,代码更简洁。
- 性能: 避免了频繁的
state
更新和重新渲染(仅在需要时读取值)。
缺点:
- 数据流不清晰: 表单数据分散在 DOM 中,不如
state
集中管理。 - 难以实时验证/转换: 无法像受控组件那样在
onChange
中轻松处理。 - 难以动态控制: 从 React 代码中改变非受控组件的值比较麻烦(需要操作
ref
)。 - 与 React 哲学不完全一致: 将状态管理交给了 DOM。
示例:
jsx
import React, { useRef } from 'react';
function UncontrolledInput() {
const nameInputRef = useRef(null); // 创建 ref
const handleSubmit = (event) => {
event.preventDefault();
// 通过 ref 获取 DOM 元素的值
console.log('提交的姓名:', nameInputRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<label>
姓名:
<input
type="text"
ref={nameInputRef} // 将 ref 附加到 input
defaultValue="初始值" // 设置默认值(只在首次渲染时有效)
/>
</label>
<button type="submit">提交</button>
</form>
);
}
总结对比表
特性 | 受控组件 (Controlled) | 非受控组件 (Uncontrolled) |
---|---|---|
状态管理 | React state |
DOM 自身 |
值来源 | value 属性绑定到 state |
通过 ref 读取 DOM 的 value |
初始值 | state 的初始值 |
defaultValue 属性 |
获取值时机 | 任何时刻(通过 state ) |
提交或需要时(通过 ref ) |
实时响应 | 容易(onChange ) |
困难 |
数据流 | 清晰,单向(React) | 分散(DOM) |
代码复杂度 | 相对较高(需 state 和 onChange ) |
相对较低 |
适用场景 | 需要实时验证、格式化、动态控制、复杂表单逻辑 | 简单表单、文件上传、集成非 React 代码、性能敏感且无需实时控制的场景 |
一般建议: 在大多数情况下,优先使用受控组件 ,因为它更符合 React 的设计理念,数据流更清晰,功能更强大。只有在特定需求(如处理文件输入 input[type="file"]
必须是非受控的,或者性能要求极高且逻辑简单)时才考虑使用非受控组件。