Appearance
受控与非受控组件深入
概述
表单是Web应用中最重要的用户交互方式之一。在React中,处理表单有两种主要方式:受控组件和非受控组件。理解这两种方式的原理、使用场景和最佳实践,是掌握React表单开发的基础。本文将深入探讨受控与非受控组件的所有细节。
受控组件
基础概念
受控组件是指表单元素的值由React的state控制,每次值改变都会触发state更新,state更新又会重新渲染组件。
jsx
import { useState } from 'react';
// 基础受控输入框
function ControlledInput() {
const [value, setValue] = useState('');
return (
<div>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<p>当前值: {value}</p>
</div>
);
}
// 受控文本域
function ControlledTextarea() {
const [text, setText] = useState('');
return (
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
rows={5}
/>
);
}
// 受控选择框
function ControlledSelect() {
const [selected, setSelected] = useState('');
return (
<select
value={selected}
onChange={(e) => setSelected(e.target.value)}
>
<option value="">请选择</option>
<option value="option1">选项1</option>
<option value="option2">选项2</option>
<option value="option3">选项3</option>
</select>
);
}
// 受控复选框
function ControlledCheckbox() {
const [checked, setChecked] = useState(false);
return (
<label>
<input
type="checkbox"
checked={checked}
onChange={(e) => setChecked(e.target.checked)}
/>
同意条款
</label>
);
}
// 受控单选按钮组
function ControlledRadio() {
const [gender, setGender] = useState('');
return (
<div>
<label>
<input
type="radio"
value="male"
checked={gender === 'male'}
onChange={(e) => setGender(e.target.value)}
/>
男
</label>
<label>
<input
type="radio"
value="female"
checked={gender === 'female'}
onChange={(e) => setGender(e.target.value)}
/>
女
</label>
</div>
);
}受控组件的优势
jsx
// 1. 实时验证
function ValidatedInput() {
const [email, setEmail] = useState('');
const [error, setError] = useState('');
const validateEmail = (value) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
setError('请输入有效的邮箱地址');
} else {
setError('');
}
};
const handleChange = (e) => {
const value = e.target.value;
setEmail(value);
validateEmail(value);
};
return (
<div>
<input
type="email"
value={email}
onChange={handleChange}
className={error ? 'error' : ''}
/>
{error && <span className="error-message">{error}</span>}
</div>
);
}
// 2. 格式化输入
function FormattedPhoneInput() {
const [phone, setPhone] = useState('');
const formatPhone = (value) => {
// 移除所有非数字字符
const numbers = value.replace(/\D/g, '');
// 限制长度
const limited = numbers.slice(0, 11);
// 格式化为 XXX-XXXX-XXXX
if (limited.length <= 3) {
return limited;
} else if (limited.length <= 7) {
return `${limited.slice(0, 3)}-${limited.slice(3)}`;
} else {
return `${limited.slice(0, 3)}-${limited.slice(3, 7)}-${limited.slice(7)}`;
}
};
const handleChange = (e) => {
const formatted = formatPhone(e.target.value);
setPhone(formatted);
};
return (
<input
type="tel"
value={phone}
onChange={handleChange}
placeholder="XXX-XXXX-XXXX"
/>
);
}
// 3. 条件禁用
function ConditionalForm() {
const [formData, setFormData] = useState({
agreeTerms: false,
age: '',
});
const canSubmit = formData.agreeTerms && parseInt(formData.age) >= 18;
return (
<form>
<input
type="number"
value={formData.age}
onChange={(e) => setFormData({ ...formData, age: e.target.value })}
placeholder="年龄"
/>
<label>
<input
type="checkbox"
checked={formData.agreeTerms}
onChange={(e) => setFormData({ ...formData, agreeTerms: e.target.checked })}
/>
我已满18岁并同意条款
</label>
<button type="submit" disabled={!canSubmit}>
提交
</button>
</form>
);
}
// 4. 动态表单
function DynamicFieldForm() {
const [fields, setFields] = useState([{ id: 1, value: '' }]);
const addField = () => {
setFields([...fields, { id: Date.now(), value: '' }]);
};
const removeField = (id) => {
setFields(fields.filter(field => field.id !== id));
};
const updateField = (id, value) => {
setFields(fields.map(field =>
field.id === id ? { ...field, value } : field
));
};
return (
<div>
{fields.map(field => (
<div key={field.id}>
<input
type="text"
value={field.value}
onChange={(e) => updateField(field.id, e.target.value)}
/>
<button onClick={() => removeField(field.id)}>删除</button>
</div>
))}
<button onClick={addField}>添加字段</button>
</div>
);
}受控组件的挑战
jsx
// 1. 性能问题 - 频繁渲染
function PerformanceIssue() {
const [value, setValue] = useState('');
console.log('组件重新渲染'); // 每次输入都会打印
return (
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
}
// 解决方案: 使用防抖
import { useState, useCallback } from 'react';
import { debounce } from 'lodash';
function OptimizedInput() {
const [displayValue, setDisplayValue] = useState('');
const [searchResults, setSearchResults] = useState([]);
// 防抖搜索
const debouncedSearch = useCallback(
debounce((value) => {
// 执行搜索
performSearch(value).then(setSearchResults);
}, 300),
[]
);
const handleChange = (e) => {
const value = e.target.value;
setDisplayValue(value);
debouncedSearch(value);
};
return (
<div>
<input
type="text"
value={displayValue}
onChange={handleChange}
/>
<SearchResults results={searchResults} />
</div>
);
}
// 2. 复杂状态管理
function ComplexForm() {
const [formData, setFormData] = useState({
personal: {
firstName: '',
lastName: '',
email: '',
},
address: {
street: '',
city: '',
zip: '',
},
preferences: {
newsletter: false,
notifications: true,
},
});
// 嵌套更新比较复杂
const updatePersonal = (field, value) => {
setFormData({
...formData,
personal: {
...formData.personal,
[field]: value,
},
});
};
// 使用immer简化嵌套更新
import { useImmer } from 'use-immer';
const [data, updateData] = useImmer({
personal: { firstName: '', lastName: '' },
address: { street: '', city: '' },
});
const updateField = (section, field, value) => {
updateData(draft => {
draft[section][field] = value;
});
};
return (
<input
value={data.personal.firstName}
onChange={(e) => updateField('personal', 'firstName', e.target.value)}
/>
);
}非受控组件
基础概念
非受控组件是指表单元素的值由DOM本身管理,React通过ref来访问DOM元素并获取值。
jsx
import { useRef } from 'react';
// 基础非受控输入框
function UncontrolledInput() {
const inputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
console.log('输入值:', inputRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
ref={inputRef}
defaultValue="初始值"
/>
<button type="submit">提交</button>
</form>
);
}
// 非受控文本域
function UncontrolledTextarea() {
const textareaRef = useRef(null);
const getValue = () => {
return textareaRef.current.value;
};
return (
<textarea
ref={textareaRef}
defaultValue="默认文本"
rows={5}
/>
);
}
// 非受控选择框
function UncontrolledSelect() {
const selectRef = useRef(null);
const getSelected = () => {
return selectRef.current.value;
};
return (
<select ref={selectRef} defaultValue="option2">
<option value="option1">选项1</option>
<option value="option2">选项2</option>
<option value="option3">选项3</option>
</select>
);
}
// 非受控复选框
function UncontrolledCheckbox() {
const checkboxRef = useRef(null);
const isChecked = () => {
return checkboxRef.current.checked;
};
return (
<label>
<input
type="checkbox"
ref={checkboxRef}
defaultChecked={true}
/>
记住我
</label>
);
}
// 文件输入(总是非受控)
function FileInput() {
const fileInputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
const files = fileInputRef.current.files;
console.log('选择的文件:', files);
};
return (
<form onSubmit={handleSubmit}>
<input
type="file"
ref={fileInputRef}
multiple
/>
<button type="submit">上传</button>
</form>
);
}非受控组件的优势
jsx
// 1. 性能优化 - 减少渲染
function PerformantForm() {
const nameRef = useRef(null);
const emailRef = useRef(null);
const messageRef = useRef(null);
// 表单提交时才读取值,避免频繁渲染
const handleSubmit = (e) => {
e.preventDefault();
const formData = {
name: nameRef.current.value,
email: emailRef.current.value,
message: messageRef.current.value,
};
console.log('表单数据:', formData);
// 提交数据...
};
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={nameRef} placeholder="姓名" />
<input type="email" ref={emailRef} placeholder="邮箱" />
<textarea ref={messageRef} placeholder="消息" />
<button type="submit">提交</button>
</form>
);
}
// 2. 与第三方库集成
function IntegrateWithLibrary() {
const editorRef = useRef(null);
useEffect(() => {
// 初始化第三方编辑器
const editor = new SomeEditor(editorRef.current, {
// 配置...
});
return () => {
editor.destroy();
};
}, []);
const getValue = () => {
// 通过ref获取第三方库的值
return editorRef.current.getContent();
};
return <div ref={editorRef} />;
}
// 3. 表单重置更简单
function ResetForm() {
const formRef = useRef(null);
const handleReset = () => {
formRef.current.reset();
};
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(formRef.current);
console.log(Object.fromEntries(formData));
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
<input type="text" name="username" defaultValue="" />
<input type="email" name="email" defaultValue="" />
<button type="submit">提交</button>
<button type="button" onClick={handleReset}>重置</button>
</form>
);
}非受控组件的局限
jsx
// 1. 难以实现实时验证
function UncontrolledValidation() {
const emailRef = useRef(null);
const [error, setError] = useState('');
// 只能在blur或submit时验证
const handleBlur = () => {
const value = emailRef.current.value;
if (!value.includes('@')) {
setError('请输入有效的邮箱');
} else {
setError('');
}
};
return (
<div>
<input
type="email"
ref={emailRef}
onBlur={handleBlur}
/>
{error && <span>{error}</span>}
</div>
);
}
// 2. 难以实现条件渲染
function ConditionalRenderingIssue() {
const ageRef = useRef(null);
const [showAdultContent, setShowAdultContent] = useState(false);
// 需要额外的事件处理来检查值
const handleChange = () => {
const age = parseInt(ageRef.current.value);
setShowAdultContent(age >= 18);
};
return (
<div>
<input
type="number"
ref={ageRef}
onChange={handleChange}
/>
{showAdultContent && <div>成人内容</div>}
</div>
);
}
// 3. 难以同步多个输入
function SyncInputsIssue() {
const input1Ref = useRef(null);
const input2Ref = useRef(null);
// 无法轻松保持两个输入同步
const handleInput1Change = () => {
// 需要手动设置另一个输入的值
input2Ref.current.value = input1Ref.current.value;
};
return (
<div>
<input ref={input1Ref} onChange={handleInput1Change} />
<input ref={input2Ref} />
</div>
);
}混合使用策略
最佳实践场景
jsx
// 场景1: 简单表单 - 使用非受控
function SimpleContactForm() {
const formRef = useRef(null);
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData(formRef.current);
await fetch('/api/contact', {
method: 'POST',
body: formData,
});
formRef.current.reset();
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
<input type="text" name="name" required />
<input type="email" name="email" required />
<textarea name="message" required />
<button type="submit">发送</button>
</form>
);
}
// 场景2: 需要验证的表单 - 使用受控
function ValidatedRegistrationForm() {
const [formData, setFormData] = useState({
username: '',
password: '',
confirmPassword: '',
});
const [errors, setErrors] = useState({});
const validate = () => {
const newErrors = {};
if (formData.username.length < 3) {
newErrors.username = '用户名至少3个字符';
}
if (formData.password.length < 6) {
newErrors.password = '密码至少6个字符';
}
if (formData.password !== formData.confirmPassword) {
newErrors.confirmPassword = '密码不匹配';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e) => {
e.preventDefault();
if (validate()) {
console.log('提交表单:', formData);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={formData.username}
onChange={(e) => setFormData({ ...formData, username: e.target.value })}
/>
{errors.username && <span>{errors.username}</span>}
<input
type="password"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
/>
{errors.password && <span>{errors.password}</span>}
<input
type="password"
value={formData.confirmPassword}
onChange={(e) => setFormData({ ...formData, confirmPassword: e.target.value })}
/>
{errors.confirmPassword && <span>{errors.confirmPassword}</span>}
<button type="submit">注册</button>
</form>
);
}
// 场景3: 混合使用
function HybridForm() {
// 需要实时反馈的字段用受控
const [email, setEmail] = useState('');
const [emailValid, setEmailValid] = useState(false);
// 不需要实时反馈的字段用非受控
const nameRef = useRef(null);
const phoneRef = useRef(null);
useEffect(() => {
const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
setEmailValid(isValid);
}, [email]);
const handleSubmit = (e) => {
e.preventDefault();
const formData = {
name: nameRef.current.value,
phone: phoneRef.current.value,
email: email,
};
console.log('提交:', formData);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={nameRef} placeholder="姓名" />
<input type="tel" ref={phoneRef} placeholder="电话" />
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="邮箱"
/>
{email && (
<span>{emailValid ? '✓ 有效' : '✗ 无效'}</span>
)}
<button type="submit">提交</button>
</form>
);
}转换策略
jsx
// 受控转非受控
function ControlledToUncontrolled() {
// 受控版本
const [value, setValue] = useState('initial');
return (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
// 转换为非受控版本
const inputRef = useRef(null);
return (
<input
ref={inputRef}
defaultValue="initial"
/>
);
}
// 非受控转受控
function UncontrolledToControlled() {
// 非受控版本
const inputRef = useRef(null);
return (
<input
ref={inputRef}
defaultValue="initial"
/>
);
// 转换为受控版本
const [value, setValue] = useState('initial');
return (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
}
// 动态切换(避免)
function AvoidDynamicSwitch() {
const [controlled, setControlled] = useState(true);
const [value, setValue] = useState('');
// ❌ 不要这样做 - 会导致警告
return (
<input
value={controlled ? value : undefined}
onChange={(e) => setValue(e.target.value)}
/>
);
// ✅ 正确做法 - 保持一致
return (
<input
value={value || ''}
onChange={(e) => setValue(e.target.value)}
/>
);
}高级模式
受控组件高级技巧
jsx
// 1. 受控组件工厂
function createControlledInput(Component) {
return function ControlledWrapper({ value, onChange, ...props }) {
const [internalValue, setInternalValue] = useState(value || '');
useEffect(() => {
if (value !== undefined) {
setInternalValue(value);
}
}, [value]);
const handleChange = (e) => {
const newValue = e.target.value;
setInternalValue(newValue);
onChange?.(newValue);
};
return (
<Component
{...props}
value={internalValue}
onChange={handleChange}
/>
);
};
}
const ControlledInput = createControlledInput('input');
const ControlledTextarea = createControlledInput('textarea');
// 2. 延迟受控
function DelayedControlledInput({ value, onChange, delay = 300 }) {
const [displayValue, setDisplayValue] = useState(value);
const timeoutRef = useRef(null);
useEffect(() => {
setDisplayValue(value);
}, [value]);
const handleChange = (e) => {
const newValue = e.target.value;
setDisplayValue(newValue);
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
onChange(newValue);
}, delay);
};
return (
<input
type="text"
value={displayValue}
onChange={handleChange}
/>
);
}
// 3. 受控组件with缓存
function CachedControlledInput({ value, onChange }) {
const [cache, setCache] = useState(value);
const [isDirty, setIsDirty] = useState(false);
const handleChange = (e) => {
const newValue = e.target.value;
setCache(newValue);
setIsDirty(newValue !== value);
};
const handleSave = () => {
onChange(cache);
setIsDirty(false);
};
const handleCancel = () => {
setCache(value);
setIsDirty(false);
};
return (
<div>
<input
type="text"
value={cache}
onChange={handleChange}
/>
{isDirty && (
<>
<button onClick={handleSave}>保存</button>
<button onClick={handleCancel}>取消</button>
</>
)}
</div>
);
}非受控组件高级技巧
jsx
// 1. 带默认值的非受控组件
function UncontrolledWithDefault({ defaultValue, onSubmit }) {
const inputRef = useRef(null);
const [mounted, setMounted] = useState(false);
useEffect(() => {
if (!mounted && defaultValue) {
inputRef.current.value = defaultValue;
setMounted(true);
}
}, [defaultValue, mounted]);
const handleSubmit = () => {
onSubmit(inputRef.current.value);
};
return (
<div>
<input ref={inputRef} />
<button onClick={handleSubmit}>提交</button>
</div>
);
}
// 2. 非受控组件监听器
function UncontrolledWithListener({ onChange }) {
const inputRef = useRef(null);
useEffect(() => {
const input = inputRef.current;
const handleInput = (e) => {
onChange?.(e.target.value);
};
input.addEventListener('input', handleInput);
return () => {
input.removeEventListener('input', handleInput);
};
}, [onChange]);
return <input ref={inputRef} />;
}
// 3. 非受控表单验证
function UncontrolledFormValidation() {
const formRef = useRef(null);
const [errors, setErrors] = useState({});
const validate = () => {
const form = formRef.current;
const formData = new FormData(form);
const newErrors = {};
const email = formData.get('email');
if (!email.includes('@')) {
newErrors.email = '无效的邮箱';
}
const age = formData.get('age');
if (age < 18) {
newErrors.age = '必须年满18岁';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e) => {
e.preventDefault();
if (validate()) {
const formData = new FormData(formRef.current);
console.log(Object.fromEntries(formData));
}
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
<input type="email" name="email" />
{errors.email && <span>{errors.email}</span>}
<input type="number" name="age" />
{errors.age && <span>{errors.age}</span>}
<button type="submit">提交</button>
</form>
);
}性能优化
受控组件优化
jsx
// 1. 使用useCallback避免重新创建函数
function OptimizedControlled() {
const [value, setValue] = useState('');
const handleChange = useCallback((e) => {
setValue(e.target.value);
}, []);
return (
<input
type="text"
value={value}
onChange={handleChange}
/>
);
}
// 2. 分离子组件减少渲染范围
const Input = React.memo(function Input({ value, onChange }) {
console.log('Input渲染');
return (
<input
type="text"
value={value}
onChange={onChange}
/>
);
});
function FormWithMemo() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const handleNameChange = useCallback((e) => {
setName(e.target.value);
}, []);
const handleEmailChange = useCallback((e) => {
setEmail(e.target.value);
}, []);
return (
<div>
<Input value={name} onChange={handleNameChange} />
<Input value={email} onChange={handleEmailChange} />
</div>
);
}
// 3. 使用防抖节流
import { useDebouncedCallback } from 'use-debounce';
function DebouncedSearch() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const debouncedSearch = useDebouncedCallback(
(value) => {
performSearch(value).then(setResults);
},
300
);
const handleChange = (e) => {
const value = e.target.value;
setQuery(value);
debouncedSearch(value);
};
return (
<div>
<input
type="text"
value={query}
onChange={handleChange}
/>
<Results data={results} />
</div>
);
}
// 4. 虚拟化长列表
import { FixedSizeList } from 'react-window';
function VirtualizedControlledList() {
const [items, setItems] = useState(
Array.from({ length: 10000 }, (_, i) => ({
id: i,
checked: false,
}))
);
const handleToggle = (id) => {
setItems(items.map(item =>
item.id === id ? { ...item, checked: !item.checked } : item
));
};
const Row = ({ index, style }) => (
<div style={style}>
<input
type="checkbox"
checked={items[index].checked}
onChange={() => handleToggle(items[index].id)}
/>
Item {items[index].id}
</div>
);
return (
<FixedSizeList
height={400}
itemCount={items.length}
itemSize={35}
width="100%"
>
{Row}
</FixedSizeList>
);
}非受控组件优化
jsx
// 1. 批量读取表单值
function BatchRead() {
const formRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
// 一次性读取所有值
const formData = new FormData(formRef.current);
const values = Object.fromEntries(formData);
console.log(values);
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
<input type="text" name="field1" />
<input type="text" name="field2" />
<input type="text" name="field3" />
<button type="submit">提交</button>
</form>
);
}
// 2. 使用原生表单验证
function NativeValidation() {
const formRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
if (formRef.current.checkValidity()) {
const formData = new FormData(formRef.current);
console.log(Object.fromEntries(formData));
}
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
<input
type="email"
name="email"
required
pattern="[^@]+@[^@]+\.[^@]+"
/>
<input
type="number"
name="age"
required
min="18"
max="100"
/>
<button type="submit">提交</button>
</form>
);
}实战案例
搜索表单
jsx
// 受控版本 - 实时搜索
function ControlledSearchForm() {
const [searchTerm, setSearchTerm] = useState('');
const [filters, setFilters] = useState({
category: '',
priceRange: '',
inStock: false,
});
const [results, setResults] = useState([]);
useEffect(() => {
const delayDebounce = setTimeout(() => {
if (searchTerm) {
performSearch(searchTerm, filters).then(setResults);
}
}, 300);
return () => clearTimeout(delayDebounce);
}, [searchTerm, filters]);
return (
<div>
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="搜索..."
/>
<select
value={filters.category}
onChange={(e) => setFilters({ ...filters, category: e.target.value })}
>
<option value="">所有分类</option>
<option value="electronics">电子产品</option>
<option value="clothing">服装</option>
</select>
<label>
<input
type="checkbox"
checked={filters.inStock}
onChange={(e) => setFilters({ ...filters, inStock: e.target.checked })}
/>
仅显示有货
</label>
<SearchResults results={results} />
</div>
);
}
// 非受控版本 - 提交时搜索
function UncontrolledSearchForm() {
const formRef = useRef(null);
const [results, setResults] = useState([]);
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData(formRef.current);
const searchParams = {
term: formData.get('searchTerm'),
category: formData.get('category'),
inStock: formData.get('inStock') === 'on',
};
const data = await performSearch(searchParams);
setResults(data);
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
<input
type="text"
name="searchTerm"
placeholder="搜索..."
/>
<select name="category">
<option value="">所有分类</option>
<option value="electronics">电子产品</option>
<option value="clothing">服装</option>
</select>
<label>
<input type="checkbox" name="inStock" />
仅显示有货
</label>
<button type="submit">搜索</button>
<SearchResults results={results} />
</form>
);
}多步骤表单
jsx
// 混合策略
function MultiStepForm() {
const [step, setStep] = useState(1);
const [formData, setFormData] = useState({});
// 第一步 - 受控(需要验证)
const Step1 = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [errors, setErrors] = useState({});
const validateStep1 = () => {
const newErrors = {};
if (!email.includes('@')) newErrors.email = '无效的邮箱';
if (password.length < 6) newErrors.password = '密码至少6位';
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleNext = () => {
if (validateStep1()) {
setFormData({ ...formData, email, password });
setStep(2);
}
};
return (
<div>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
{errors.email && <span>{errors.email}</span>}
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
{errors.password && <span>{errors.password}</span>}
<button onClick={handleNext}>下一步</button>
</div>
);
};
// 第二步 - 非受控(不需要实时验证)
const Step2 = () => {
const formRef = useRef(null);
const handleNext = () => {
const data = new FormData(formRef.current);
setFormData({
...formData,
firstName: data.get('firstName'),
lastName: data.get('lastName'),
phone: data.get('phone'),
});
setStep(3);
};
return (
<form ref={formRef}>
<input type="text" name="firstName" required />
<input type="text" name="lastName" required />
<input type="tel" name="phone" required />
<button type="button" onClick={() => setStep(1)}>上一步</button>
<button type="button" onClick={handleNext}>下一步</button>
</form>
);
};
// 第三步 - 确认
const Step3 = () => {
const handleSubmit = async () => {
await submitForm(formData);
};
return (
<div>
<h3>确认信息</h3>
<pre>{JSON.stringify(formData, null, 2)}</pre>
<button onClick={() => setStep(2)}>上一步</button>
<button onClick={handleSubmit}>提交</button>
</div>
);
};
return (
<div>
{step === 1 && <Step1 />}
{step === 2 && <Step2 />}
{step === 3 && <Step3 />}
</div>
);
}总结
受控与非受控组件选择要点:
- 受控组件:需要实时验证、格式化、条件渲染时使用
- 非受控组件:简单表单、性能敏感、第三方库集成时使用
- 混合使用:根据具体字段需求灵活选择
- 性能优化:防抖节流、memo、虚拟化
- 避免陷阱:不要动态切换受控/非受控状态
选择合适的方式,能够在功能性和性能之间取得最佳平衡。