在 React 中,受控组件和非受控组件是处理表单数据的两种不同方式,核心区别在于表单数据的管理方式。
一、核心区别
特性 | 受控组件(Controlled Component) | 非受控组件(Uncontrolled Component) |
---|---|---|
数据来源 | 由 React 的state 管理(单一数据源) |
由 DOM 自身管理(类似原生 HTML 表单) |
更新方式 | 通过setState() 更新数据 |
直接操作 DOM(通过ref 获取或修改值) |
数据同步 | 表单值与state 实时同步 |
表单值与state 不同步,需主动获取 |
适用场景 | 复杂交互(实时验证、联动表单、动态禁用等) | 简单场景(无需实时处理,仅提交时获取值) |
二、用法示例
1. 受控组件
表单元素的值由 React 的state
控制,用户输入会触发事件处理函数(如onChange
),通过setState
更新state
,进而更新表单值。
jsx
import { useState } from 'react';
function ControlledForm() {
// 用state管理表单数据
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
// 处理表单提交
const handleSubmit = (e) => {
e.preventDefault();
console.log('提交数据:', { username, email });
};
return (
<form onSubmit={handleSubmit}>
{/* 用户名:value绑定state,onChange更新state */}
<input
type="text"
value={username} // 受控于state
onChange={(e) => setUsername(e.target.value)} // 输入时更新state
placeholder="用户名"
/>
{/* 邮箱:同理 */}
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="邮箱"
/>
<button type="submit">提交</button>
</form>
);
}
特点:
- 表单值完全受 React 控制,可实时监控输入变化(如实时验证)。
- 必须配合
onChange
(或其他事件)更新state
,否则输入会被 "锁定"(无法修改)。
2. 非受控组件
表单数据由 DOM 自身管理,通过ref
直接访问 DOM 元素获取值,初始值可通过defaultValue
(或defaultChecked
for 复选框)设置。
jsx
import { useRef } from 'react';
function UncontrolledForm() {
// 创建ref关联DOM元素
const usernameRef = useRef(null);
const emailRef = useRef(null);
// 处理表单提交
const handleSubmit = (e) => {
e.preventDefault();
// 通过ref获取DOM值
const username = usernameRef.current.value;
const email = emailRef.current.value;
console.log('提交数据:', { username, email });
};
return (
<form onSubmit={handleSubmit}>
{/* 用户名:ref关联DOM,defaultValue设置初始值 */}
<input
type="text"
ref={usernameRef} // 关联ref
defaultValue="默认用户名" // 仅初始渲染生效
placeholder="用户名"
/>
{/* 邮箱:同理 */}
<input
type="email"
ref={emailRef}
defaultValue=""
placeholder="邮箱"
/>
<button type="submit">提交</button>
</form>
);
}
特点:
- 更接近原生 HTML 表单行为,代码更简洁。
- 无法实时监控输入变化,适合简单场景(如一次性提交)。
- 文件上传(
input type="file"
)只能用非受控组件(因安全限制,无法通过value
控制)。
三、受控组件的典型应用场景
受控组件适合需要实时处理表单数据 的场景,如实时验证、动态交互、数据联动等。其核心优势是能通过 state
实时掌控表单状态,便于实现复杂逻辑。
场景 1:实时验证的注册表单
注册表单中,通常需要实时检查用户名是否已存在、密码强度、邮箱格式等。这类场景必须使用受控组件,因为需要在用户输入过程中即时反馈验证结果。
jsx
import { useState } from 'react';
function RegisterForm() {
const [formData, setFormData] = useState({
username: '',
password: '',
email: ''
});
const [errors, setErrors] = useState({});
// 实时验证邮箱格式
const validateEmail = (email) => {
const regex = /^[^\s@]+@[^\s@]+.[^\s@]+$/;
return regex.test(email) ? '' : '邮箱格式不正确';
};
// 输入变化时更新state并验证
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
// 实时验证(仅示例邮箱)
if (name === 'email') {
setErrors(prev => ({ ...prev, email: validateEmail(value) }));
}
};
const handleSubmit = (e) => {
e.preventDefault();
// 提交前全量验证...
console.log('提交注册数据:', formData);
};
return (
<form onSubmit={handleSubmit}>
<input
name="username"
value={formData.username}
onChange={handleChange}
placeholder="用户名"
/>
<input
name="email"
type="email"
value={formData.email}
onChange={handleChange}
placeholder="邮箱"
/>
{errors.email && <span style={{ color: 'red' }}>{errors.email}</span>} {/* 实时显示错误 */}
<input
name="password"
type="password"
value={formData.password}
onChange={handleChange}
placeholder="密码"
/>
<button type="submit">注册</button>
</form>
);
}
为什么用受控组件?
- 需要在用户输入时即时验证并显示错误信息(依赖实时获取输入值)。
- 可能需要根据输入内容动态调整表单(如密码输入到一定长度才显示 "强度提示")。
场景 2:搜索框的实时联想提示
搜索框在用户输入过程中,需要实时请求后端接口并展示联想结果(如百度搜索的下拉提示)。这类场景必须用受控组件,因为需要监听每一次输入变化并触发后续逻辑。
jsx
import { useState, useEffect } from 'react';
function SearchBox() {
const [searchText, setSearchText] = useState('');
const [suggestions, setSuggestions] = useState([]);
// 输入变化时更新state,并请求联想接口
useEffect(() => {
if (searchText.length < 2) {
setSuggestions([]);
return;
}
// 模拟接口请求:每输入一个字符就触发一次
const timer = setTimeout(() => {
fetch(`/api/suggest?keyword=${searchText}`)
.then(res => res.json())
.then(data => setSuggestions(data));
}, 300); // 防抖处理
return () => clearTimeout(timer);
}, [searchText]); // 依赖searchText,输入变化时重新执行
return (
<div>
<input
type="text"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
placeholder="搜索..."
/>
<ul>
{suggestions.map((item) => (
<li key={item.id}>{item.text}</li>
))}
</ul>
</div>
);
}
为什么用受控组件?
- 必须实时获取用户输入的
searchText
,才能触发联想接口请求。 - 联想结果的展示状态(
suggestions
)与输入值强关联,需通过state
同步管理。
四、非受控组件的典型应用场景
非受控组件适合无需实时处理数据 的简单场景,仅在提交 / 操作时获取值即可。其优势是代码更简洁,减少state
和事件处理的冗余。
场景 1:简单的登录弹窗
登录弹窗通常只需要在用户点击 "登录" 按钮时,一次性获取用户名和密码,无需实时验证(或仅在提交时验证)。这类场景用非受控组件更简洁。
jsx
import { useRef } from 'react';
function LoginModal() {
// 用ref关联DOM元素
const usernameRef = useRef(null);
const passwordRef = useRef(null);
const handleLogin = () => {
// 仅在点击登录时获取值
const username = usernameRef.current.value;
const password = passwordRef.current.value;
// 提交验证
if (!username || !password) {
alert('请输入用户名和密码');
return;
}
console.log('登录请求:', { username, password });
};
return (
<div className="modal">
<input
ref={usernameRef}
type="text"
defaultValue=""
placeholder="用户名"
/>
<input
ref={passwordRef}
type="password"
defaultValue=""
placeholder="密码"
/>
<button onClick={handleLogin}>登录</button>
</div>
);
}
为什么用非受控组件?
- 无需实时处理输入(如输入过程中不验证),仅在提交时获取值即可。
- 减少
state
和onChange
的定义,代码更简洁。
场景 2:文件上传功能
由于浏览器安全限制,<input type="file">
的值无法通过 JavaScript 设置(value
属性只读),只能通过ref
获取用户选择的文件。因此,文件上传必须使用非受控组件。
jsx
import { useRef } from 'react';
function FileUploader() {
const fileInputRef = useRef(null);
const handleUpload = () => {
// 通过ref获取用户选择的文件
const selectedFile = fileInputRef.current.files[0];
if (!selectedFile) {
alert('请选择文件');
return;
}
console.log('上传文件:', selectedFile);
// 模拟文件上传(实际中会用FormData)
const formData = new FormData();
formData.append('file', selectedFile);
// fetch('/api/upload', { method: 'POST', body: formData });
};
return (
<div>
{/* 文件输入必须用非受控组件 */}
<input
ref={fileInputRef}
type="file"
accept="image/*"
/>
<button onClick={handleUpload}>上传</button>
</div>
);
}
为什么用非受控组件?
- 浏览器限制:
input[type="file"]
的value
无法通过state
设置,只能通过ref
访问用户选择的文件。 - 无需实时处理,仅在用户点击 "上传" 时获取文件即可。
五、如何选择?
- 受控组件 :优先用于需要实时处理数据 的场景(验证、联动、实时反馈等),核心是通过
state
掌控数据流向。 - 非受控组件 :适合简单交互 场景(仅提交时获取值)或浏览器限制场景 (如文件上传),核心是通过
ref
直接操作 DOM。
实际开发中,受控组件的使用频率更高,因为它更符合 React"数据驱动视图" 的设计理念,尤其在复杂表单中能显著提升可维护性。