React 表单:受控组件与非受控组件
在 React 开发中,处理表单输入是核心交互之一。根据数据由谁(React State 还是 DOM)来持有“唯一真实数据源”,我们将表单组件分为受控组件和非受控组件。理解并正确使用这两种模式,能让你在处理复杂表单验证时游刃有余,或者在快速搭建简单表单时更加高效。
第一部分:受控组件
受控组件是 React 推荐的表单处理方式。在这种模式下,表单数据的“唯一真实数据源”是 React 组件内部的 State。这意味着输入框的值完全由 State 控制,用户的每一次输入都会触发 State 的更新,进而更新视图。
实现步骤
-
定义状态变量。
在组件内部使用useStateHook 定义一个变量,用于存储输入框的值。 -
绑定值属性。
将输入框(如<input>)的value属性设置为第一步定义的状态变量。 -
监听变化事件。
为输入框添加onChange事件监听器。当用户输入时,该事件会触发。 -
更新状态。
在事件处理函数中,通过event.target.value获取用户输入,并调用 State 更新函数来同步数据。
代码示例
import React, { useState } from 'react';
function ControlledForm() {
// 1. 定义状态
const [username, setUsername] = useState('');
// 3. & 4. 监听变化并更新状态
const handleChange = (event) => {
setUsername(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
// 提交时直接使用 State 中的数据
console.log('提交的用户名:', username);
};
return (
<form onSubmit={handleSubmit}>
<label>
用户名:
{/* 2. 绑定 value */}
<input
type="text"
value={username}
onChange={handleChange}
/>
</label>
<button type="submit">提交</button>
</form>
);
}
数据流转逻辑
受控组件的数据流转形成一个闭环:用户输入触发事件,事件更新 State,State 更新触发重新渲染,重新渲染将新值回填到输入框。
核心特点与适用场景
- 实时性:可以即时对输入进行验证(如限制字符长度、格式检查)。
- 统一性:所有输入数据都集中在 State 中,方便传递给其他组件或处理。
- 适用场景:需要进行动态验证、多输入联动(如确认密码)、或者有条件提交(如输入正确才亮起按钮)的复杂表单。
第二部分:非受控组件
非受控组件类似传统的 HTML 表单行为。表单数据由 DOM 元素本身管理,而不是由 React 组件的 State 控制。要获取输入的值,你需要使用 ref(引用)来“拉取” DOM 节点的数据。
实现步骤
-
创建引用。
使用useRefHook 创建一个 ref 对象。 -
关联 DOM 节点。
将创建的 ref 对象通过ref属性附加到 JSX 的输入框元素上。 -
获取数据。
在需要数据的时候(通常是提交表单时),通过ref.current.value直接读取 DOM 中的值。
代码示例
import React, { useRef } from 'react';
function UncontrolledForm() {
// 1. 创建 ref
const inputRef = useRef(null);
const handleSubmit = (event) => {
event.preventDefault();
// 3. 直接从 DOM 获取值
const name = inputRef.current.value;
console.log('提交的用户名:', name);
};
return (
<form onSubmit={handleSubmit}>
<label>
用户名:
{/* 2. 关联 ref */}
<input
type="text"
defaultValue="初始值"
ref={inputRef}
/>
</label>
<button type="submit">提交</button>
</form>
);
}
核心注意点
- 使用
defaultValue:在非受控组件中,如果要设置初始值,必须使用defaultValue属性,而不是value属性(否则会变成只读)。 - 数据读取时机:由于数据不随输入实时更新到 State,通常只在提交(
onSubmit)时读取一次。
适用场景
- 简单表单:如一次性登录框,无需实时验证。
- 集成第三方库:当使用某些非 React 编写且直接操作 DOM 的库时。
- 性能考量:在极其庞大的表单中,避免每次击键都触发 State 更新和重渲染(虽然现代 React 性能通常已足够,但在极端场景下非受控更优)。
第三部分:对比与选择
为了在实际开发中快速做出决策,可以参考下表对两种模式进行直观对比。
| 特性 | 受控组件 | 非受控组件 |
|---|---|---|
| 数据源 | React State | DOM |
| 获取值方式 | 通过 State 变量 | 通过 ref.current.value |
| 实时验证 | 支持(每输入一个字符都能触发) | 不支持(通常在提交时验证) |
| 代码量 | 较多(需编写 Handler 和 State) | 较少(无需编写 Handler) |
| 受控性 | 强(React 完全掌控 UI) | 弱(依赖 DOM 默认行为) |
决策逻辑指南
在开始编写代码前,判断以下条件:
- 检查是否需要对用户的每一次输入进行即时反馈(如“密码强度:弱”)?
- 是:使用受控组件。
- 检查表单字段是否非常多,且不需要联动或即时验证?
- 是:考虑非受控组件以减少渲染压力。
- 检查是否需要根据输入值动态控制其他 UI 元素(如输入正确时解锁按钮)?
- 是:使用受控组件。
- 检查是否仅仅是简单的数据收集(如搜索框、基础登录)?
- 是:使用非受控组件以快速完成开发。
混合使用策略
在一个复杂的 React 应用中,不需要非黑即白。你可以在同一个表单中混合使用这两种模式。例如,用户名和密码使用受控组件以便做实时长度验证和格式校验,而文件上传字段使用非受控组件(因为文件操作必须依赖 DOM 的 File 对象)。
function MixedForm() {
const [text, setText] = useState(''); // 受控
const fileInputRef = useRef(null); // 非受控
const handleSubmit = (e) => {
e.preventDefault();
console.log('文本:', text);
console.log('文件:', fileInputRef.current.files[0]);
};
return (
<form onSubmit={handleSubmit}>
{/* 受控输入 */}
<input value={text} onChange={e => setText(e.target.value)} />
{/* 非受控文件输入 */}
<input type="file" ref={fileInputRef} />
<button>提交</button>
</form>
);
}
暂无评论,快来抢沙发吧!