Appearance
多个表单元素处理
学习目标
通过本章学习,你将全面掌握:
- 管理多个表单元素的最佳策略
- 通用的表单处理模式
- 动态表单字段的实现
- 表单数组字段的管理
- 复杂表单验证系统
- 性能优化技巧
- 表单状态管理方案
- React 19的表单处理新特性
第一部分:多字段表单管理
1.1 对象State管理
jsx
function MultiFieldForm() {
const [formData, setFormData] = useState({
// 基本信息
username: '',
email: '',
password: '',
confirmPassword: '',
// 个人信息
firstName: '',
lastName: '',
age: 0,
gender: '',
birthday: '',
// 联系信息
phone: '',
address: '',
city: '',
country: '',
zipCode: '',
// 偏好设置
newsletter: false,
notifications: false,
theme: 'light',
language: 'zh-CN'
});
// 通用的字段更新处理器
const handleChange = (field) => (e) => {
const value = e.target.type === 'checkbox'
? e.target.checked
: e.target.value;
setFormData(prev => ({
...prev,
[field]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('表单数据:', formData);
};
return (
<form onSubmit={handleSubmit}>
<section>
<h3>基本信息</h3>
<input
value={formData.username}
onChange={handleChange('username')}
placeholder="用户名"
/>
<input
type="email"
value={formData.email}
onChange={handleChange('email')}
placeholder="邮箱"
/>
<input
type="password"
value={formData.password}
onChange={handleChange('password')}
placeholder="密码"
/>
<input
type="password"
value={formData.confirmPassword}
onChange={handleChange('confirmPassword')}
placeholder="确认密码"
/>
</section>
<section>
<h3>个人信息</h3>
<input
value={formData.firstName}
onChange={handleChange('firstName')}
placeholder="名"
/>
<input
value={formData.lastName}
onChange={handleChange('lastName')}
placeholder="姓"
/>
<input
type="number"
value={formData.age}
onChange={handleChange('age')}
placeholder="年龄"
/>
<select value={formData.gender} onChange={handleChange('gender')}>
<option value="">选择性别</option>
<option value="male">男</option>
<option value="female">女</option>
<option value="other">其他</option>
</select>
<input
type="date"
value={formData.birthday}
onChange={handleChange('birthday')}
/>
</section>
<section>
<h3>联系信息</h3>
<input
type="tel"
value={formData.phone}
onChange={handleChange('phone')}
placeholder="电话"
/>
<input
value={formData.address}
onChange={handleChange('address')}
placeholder="地址"
/>
<input
value={formData.city}
onChange={handleChange('city')}
placeholder="城市"
/>
<input
value={formData.country}
onChange={handleChange('country')}
placeholder="国家"
/>
<input
value={formData.zipCode}
onChange={handleChange('zipCode')}
placeholder="邮编"
/>
</section>
<section>
<h3>偏好设置</h3>
<label>
<input
type="checkbox"
checked={formData.newsletter}
onChange={handleChange('newsletter')}
/>
订阅Newsletter
</label>
<label>
<input
type="checkbox"
checked={formData.notifications}
onChange={handleChange('notifications')}
/>
接收通知
</label>
<select value={formData.theme} onChange={handleChange('theme')}>
<option value="light">浅色主题</option>
<option value="dark">深色主题</option>
</select>
<select value={formData.language} onChange={handleChange('language')}>
<option value="zh-CN">简体中文</option>
<option value="en-US">English</option>
</select>
</section>
<button type="submit">提交</button>
</form>
);
}1.2 通用处理函数
jsx
function UnifiedHandlerForm() {
const [formData, setFormData] = useState({
text: '',
number: 0,
email: '',
password: '',
textarea: '',
select: '',
checkbox: false,
radio: '',
date: '',
time: '',
color: '#000000',
range: 50
});
// 通用的change处理器
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
};
return (
<form>
<input
name="text"
value={formData.text}
onChange={handleChange}
placeholder="文本"
/>
<input
name="number"
type="number"
value={formData.number}
onChange={handleChange}
/>
<input
name="email"
type="email"
value={formData.email}
onChange={handleChange}
placeholder="邮箱"
/>
<input
name="password"
type="password"
value={formData.password}
onChange={handleChange}
placeholder="密码"
/>
<textarea
name="textarea"
value={formData.textarea}
onChange={handleChange}
placeholder="多行文本"
/>
<select name="select" value={formData.select} onChange={handleChange}>
<option value="">选择</option>
<option value="a">选项A</option>
<option value="b">选项B</option>
</select>
<label>
<input
name="checkbox"
type="checkbox"
checked={formData.checkbox}
onChange={handleChange}
/>
复选框
</label>
<div>
<label>
<input
name="radio"
type="radio"
value="option1"
checked={formData.radio === 'option1'}
onChange={handleChange}
/>
选项1
</label>
<label>
<input
name="radio"
type="radio"
value="option2"
checked={formData.radio === 'option2'}
onChange={handleChange}
/>
选项2
</label>
</div>
<input
name="date"
type="date"
value={formData.date}
onChange={handleChange}
/>
<input
name="time"
type="time"
value={formData.time}
onChange={handleChange}
/>
<input
name="color"
type="color"
value={formData.color}
onChange={handleChange}
/>
<div>
<label>范围 ({formData.range}):</label>
<input
name="range"
type="range"
value={formData.range}
onChange={handleChange}
min="0"
max="100"
/>
</div>
</form>
);
}1.3 分组管理State
jsx
function GroupedStateForm() {
// 按功能分组
const [basicInfo, setBasicInfo] = useState({
username: '',
email: '',
password: ''
});
const [personalInfo, setPersonalInfo] = useState({
firstName: '',
lastName: '',
age: 0,
gender: ''
});
const [contactInfo, setContactInfo] = useState({
phone: '',
address: '',
city: ''
});
const [preferences, setPreferences] = useState({
newsletter: false,
notifications: true,
theme: 'light'
});
// 为每个组创建处理器
const handleBasicChange = (field) => (e) => {
setBasicInfo(prev => ({
...prev,
[field]: e.target.value
}));
};
const handlePersonalChange = (field) => (e) => {
setPersonalInfo(prev => ({
...prev,
[field]: e.target.value
}));
};
const handleContactChange = (field) => (e) => {
setContactInfo(prev => ({
...prev,
[field]: e.target.value
}));
};
const handlePreferenceChange = (field) => (e) => {
const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
setPreferences(prev => ({
...prev,
[field]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
const allData = {
...basicInfo,
...personalInfo,
...contactInfo,
...preferences
};
console.log('提交:', allData);
};
return (
<form onSubmit={handleSubmit}>
{/* 每个组独立管理,只有相关字段变化时才重新渲染 */}
<BasicInfoSection data={basicInfo} onChange={handleBasicChange} />
<PersonalInfoSection data={personalInfo} onChange={handlePersonalChange} />
<ContactInfoSection data={contactInfo} onChange={handleContactChange} />
<PreferencesSection data={preferences} onChange={handlePreferenceChange} />
<button type="submit">提交</button>
</form>
);
}
// 拆分的子组件
const BasicInfoSection = React.memo(({ data, onChange }) => {
console.log('BasicInfoSection渲染');
return (
<section>
<h3>基本信息</h3>
<input value={data.username} onChange={onChange('username')} placeholder="用户名" />
<input value={data.email} onChange={onChange('email')} placeholder="邮箱" />
<input type="password" value={data.password} onChange={onChange('password')} placeholder="密码" />
</section>
);
});第二部分:动态表单字段
2.1 动态添加/删除字段
jsx
function DynamicFields() {
const [fields, setFields] = useState([
{ id: 1, value: '', label: '字段1' }
]);
const addField = () => {
const newId = Math.max(...fields.map(f => f.id), 0) + 1;
setFields(prev => [
...prev,
{ id: newId, value: '', label: `字段${newId}` }
]);
};
const removeField = (id) => {
if (fields.length > 1) {
setFields(prev => prev.filter(field => field.id !== id));
}
};
const updateField = (id, value) => {
setFields(prev => prev.map(field =>
field.id === id ? { ...field, value } : field
));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('所有字段:', fields);
};
return (
<form onSubmit={handleSubmit}>
<h3>动态表单字段</h3>
{fields.map(field => (
<div key={field.id} className="dynamic-field">
<label>{field.label}:</label>
<input
value={field.value}
onChange={e => updateField(field.id, e.target.value)}
placeholder={`输入${field.label}`}
/>
<button
type="button"
onClick={() => removeField(field.id)}
disabled={fields.length === 1}
>
删除
</button>
</div>
))}
<button type="button" onClick={addField}>
添加字段
</button>
<button type="submit">提交</button>
</form>
);
}2.2 表单数组(联系人列表)
jsx
function ContactsForm() {
const [contacts, setContacts] = useState([
{ id: 1, name: '', phone: '', email: '', type: 'personal' }
]);
const addContact = () => {
const newId = Math.max(...contacts.map(c => c.id), 0) + 1;
setContacts(prev => [
...prev,
{ id: newId, name: '', phone: '', email: '', type: 'personal' }
]);
};
const removeContact = (id) => {
if (contacts.length > 1) {
setContacts(prev => prev.filter(c => c.id !== id));
}
};
const updateContact = (id, field, value) => {
setContacts(prev => prev.map(contact =>
contact.id === id
? { ...contact, [field]: value }
: contact
));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('联系人列表:', contacts);
};
return (
<form onSubmit={handleSubmit}>
<h3>联系人管理</h3>
{contacts.map((contact, index) => (
<div key={contact.id} className="contact-card">
<h4>联系人 {index + 1}</h4>
<input
value={contact.name}
onChange={e => updateContact(contact.id, 'name', e.target.value)}
placeholder="姓名"
/>
<input
type="tel"
value={contact.phone}
onChange={e => updateContact(contact.id, 'phone', e.target.value)}
placeholder="电话"
/>
<input
type="email"
value={contact.email}
onChange={e => updateContact(contact.id, 'email', e.target.value)}
placeholder="邮箱"
/>
<select
value={contact.type}
onChange={e => updateContact(contact.id, 'type', e.target.value)}
>
<option value="personal">个人</option>
<option value="work">工作</option>
<option value="emergency">紧急联系人</option>
</select>
<button
type="button"
onClick={() => removeContact(contact.id)}
disabled={contacts.length === 1}
>
删除
</button>
</div>
))}
<button type="button" onClick={addContact}>
添加联系人
</button>
<button type="submit">保存全部</button>
</form>
);
}2.3 嵌套表单数组(订单项)
jsx
function OrderForm() {
const [orderItems, setOrderItems] = useState([
{
id: 1,
product: '',
quantity: 1,
price: 0,
options: []
}
]);
const [availableOptions, setAvailableOptions] = useState([
'加急', '包装', '保险', '礼品卡'
]);
const addItem = () => {
const newId = Math.max(...orderItems.map(item => item.id), 0) + 1;
setOrderItems(prev => [
...prev,
{ id: newId, product: '', quantity: 1, price: 0, options: [] }
]);
};
const removeItem = (id) => {
if (orderItems.length > 1) {
setOrderItems(prev => prev.filter(item => item.id !== id));
}
};
const updateItem = (id, field, value) => {
setOrderItems(prev => prev.map(item =>
item.id === id ? { ...item, [field]: value } : item
));
};
const toggleOption = (itemId, option) => {
setOrderItems(prev => prev.map(item => {
if (item.id === itemId) {
const options = item.options.includes(option)
? item.options.filter(opt => opt !== option)
: [...item.options, option];
return { ...item, options };
}
return item;
}));
};
const calculateTotal = useMemo(() => {
return orderItems.reduce((sum, item) => {
let itemTotal = item.quantity * item.price;
// 选项加价
if (item.options.includes('加急')) itemTotal += 50;
if (item.options.includes('包装')) itemTotal += 20;
if (item.options.includes('保险')) itemTotal += 10;
return sum + itemTotal;
}, 0);
}, [orderItems]);
return (
<form>
<h3>订单管理</h3>
{orderItems.map((item, index) => (
<div key={item.id} className="order-item">
<h4>商品 {index + 1}</h4>
<input
value={item.product}
onChange={e => updateItem(item.id, 'product', e.target.value)}
placeholder="商品名称"
/>
<input
type="number"
value={item.quantity}
onChange={e => updateItem(item.id, 'quantity', Number(e.target.value))}
min="1"
placeholder="数量"
/>
<input
type="number"
value={item.price}
onChange={e => updateItem(item.id, 'price', Number(e.target.value))}
min="0"
step="0.01"
placeholder="单价"
/>
<div className="options">
<p>附加选项:</p>
{availableOptions.map(option => (
<label key={option}>
<input
type="checkbox"
checked={item.options.includes(option)}
onChange={() => toggleOption(item.id, option)}
/>
{option}
</label>
))}
</div>
<p className="item-subtotal">
小计: ¥{(item.quantity * item.price).toFixed(2)}
</p>
<button
type="button"
onClick={() => removeItem(item.id)}
disabled={orderItems.length === 1}
>
删除
</button>
</div>
))}
<button type="button" onClick={addItem}>
添加商品
</button>
<div className="order-total">
<h3>订单总计: ¥{calculateTotal.toFixed(2)}</h3>
</div>
</form>
);
}第三部分:表单验证
3.1 字段级验证
jsx
function FieldLevelValidation() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: '',
phone: '',
age: ''
});
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
// 验证规则
const validators = {
username: (value) => {
if (!value) return '用户名不能为空';
if (value.length < 3) return '用户名至少3个字符';
if (value.length > 20) return '用户名最多20个字符';
if (!/^[a-zA-Z0-9_]+$/.test(value)) return '只能包含字母、数字和下划线';
return '';
},
email: (value) => {
if (!value) return '邮箱不能为空';
if (!/\S+@\S+\.\S+/.test(value)) return '邮箱格式不正确';
return '';
},
password: (value) => {
if (!value) return '密码不能为空';
if (value.length < 8) return '密码至少8个字符';
if (!/[A-Z]/.test(value)) return '密码必须包含大写字母';
if (!/[a-z]/.test(value)) return '密码必须包含小写字母';
if (!/[0-9]/.test(value)) return '密码必须包含数字';
if (!/[!@#$%^&*]/.test(value)) return '密码必须包含特殊字符';
return '';
},
phone: (value) => {
if (!value) return '电话不能为空';
if (!/^1[3-9]\d{9}$/.test(value)) return '请输入正确的手机号';
return '';
},
age: (value) => {
if (!value) return '年龄不能为空';
const age = Number(value);
if (age < 0 || age > 120) return '年龄必须在0-120之间';
return '';
}
};
const handleChange = (field) => (e) => {
const value = e.target.value;
setFormData(prev => ({
...prev,
[field]: value
}));
// 如果字段已被访问过,实时验证
if (touched[field]) {
const error = validators[field](value);
setErrors(prev => ({
...prev,
[field]: error
}));
}
};
const handleBlur = (field) => () => {
setTouched(prev => ({
...prev,
[field]: true
}));
const error = validators[field](formData[field]);
setErrors(prev => ({
...prev,
[field]: error
}));
};
const handleSubmit = (e) => {
e.preventDefault();
// 标记所有字段为已访问
const allTouched = {};
Object.keys(formData).forEach(key => {
allTouched[key] = true;
});
setTouched(allTouched);
// 验证所有字段
const allErrors = {};
Object.keys(formData).forEach(field => {
const error = validators[field](formData[field]);
if (error) {
allErrors[field] = error;
}
});
setErrors(allErrors);
// 检查是否有错误
if (Object.keys(allErrors).length === 0) {
console.log('提交数据:', formData);
alert('提交成功!');
} else {
alert('请检查表单错误');
}
};
const isFormValid = Object.keys(formData).every(field =>
touched[field] && !errors[field]
);
return (
<form onSubmit={handleSubmit} className="validated-form">
{Object.keys(formData).map(field => (
<div key={field} className="form-field">
<label>{field}:</label>
<input
type={field === 'password' ? 'password' : field === 'email' ? 'email' : 'text'}
value={formData[field]}
onChange={handleChange(field)}
onBlur={handleBlur(field)}
className={touched[field] && errors[field] ? 'error' : ''}
/>
{touched[field] && errors[field] && (
<span className="error-message">{errors[field]}</span>
)}
{touched[field] && !errors[field] && formData[field] && (
<span className="success-icon">✓</span>
)}
</div>
))}
<button type="submit" disabled={!isFormValid}>
提交
</button>
</form>
);
}3.2 表单级验证
jsx
function FormLevelValidation() {
const [formData, setFormData] = useState({
startDate: '',
endDate: '',
minValue: 0,
maxValue: 100,
password: '',
confirmPassword: ''
});
const [errors, setErrors] = useState({});
// 跨字段验证
const validateForm = () => {
const newErrors = {};
// 验证日期范围
if (formData.startDate && formData.endDate) {
if (new Date(formData.startDate) > new Date(formData.endDate)) {
newErrors.dateRange = '开始日期不能晚于结束日期';
}
}
// 验证数值范围
if (formData.minValue >= formData.maxValue) {
newErrors.valueRange = '最小值必须小于最大值';
}
// 验证密码匹配
if (formData.password !== formData.confirmPassword) {
newErrors.passwordMatch = '两次密码不一致';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleChange = (field) => (e) => {
setFormData(prev => ({
...prev,
[field]: e.target.value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
if (validateForm()) {
console.log('提交:', formData);
alert('提交成功!');
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<h4>日期范围:</h4>
<input
type="date"
value={formData.startDate}
onChange={handleChange('startDate')}
/>
<span>至</span>
<input
type="date"
value={formData.endDate}
onChange={handleChange('endDate')}
/>
{errors.dateRange && (
<span className="error">{errors.dateRange}</span>
)}
</div>
<div>
<h4>数值范围:</h4>
<input
type="number"
value={formData.minValue}
onChange={handleChange('minValue')}
placeholder="最小值"
/>
<span>-</span>
<input
type="number"
value={formData.maxValue}
onChange={handleChange('maxValue')}
placeholder="最大值"
/>
{errors.valueRange && (
<span className="error">{errors.valueRange}</span>
)}
</div>
<div>
<h4>密码:</h4>
<input
type="password"
value={formData.password}
onChange={handleChange('password')}
placeholder="密码"
/>
<input
type="password"
value={formData.confirmPassword}
onChange={handleChange('confirmPassword')}
placeholder="确认密码"
/>
{errors.passwordMatch && (
<span className="error">{errors.passwordMatch}</span>
)}
</div>
<button type="submit">提交</button>
</form>
);
}第四部分:性能优化
4.1 字段组件化
jsx
// 拆分为独立组件避免整体重新渲染
const FormField = React.memo(function FormField({
label,
name,
type = 'text',
value,
onChange,
onBlur,
error,
touched,
placeholder
}) {
console.log('FormField渲染:', label);
return (
<div className="form-field">
<label>{label}</label>
<input
type={type}
name={name}
value={value}
onChange={onChange}
onBlur={onBlur}
placeholder={placeholder}
className={touched && error ? 'error' : ''}
/>
{touched && error && (
<span className="error-message">{error}</span>
)}
</div>
);
});
function OptimizedForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: '',
phone: '',
address: ''
});
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
// 使用useCallback缓存处理器
const handleChange = useCallback((field) => (e) => {
setFormData(prev => ({
...prev,
[field]: e.target.value
}));
}, []);
const handleBlur = useCallback((field) => () => {
setTouched(prev => ({
...prev,
[field]: true
}));
}, []);
return (
<form>
{/* 每个字段只在自己变化时重新渲染 */}
<FormField
label="用户名"
name="username"
value={formData.username}
onChange={handleChange('username')}
onBlur={handleBlur('username')}
error={errors.username}
touched={touched.username}
/>
<FormField
label="邮箱"
name="email"
type="email"
value={formData.email}
onChange={handleChange('email')}
onBlur={handleBlur('email')}
error={errors.email}
touched={touched.email}
/>
{/* 其他字段... */}
</form>
);
}4.2 防抖输入
jsx
function DebouncedForm() {
const [immediate, setImmediate] = useState('');
const [debounced, setDebounced] = useState('');
// 防抖搜索
useEffect(() => {
const timer = setTimeout(() => {
setDebounced(immediate);
}, 500);
return () => clearTimeout(timer);
}, [immediate]);
// 执行搜索
useEffect(() => {
if (debounced) {
console.log('搜索:', debounced);
performSearch(debounced);
}
}, [debounced]);
return (
<div>
<input
value={immediate}
onChange={e => setImmediate(e.target.value)}
placeholder="输入搜索..."
/>
<p>即时值: {immediate}</p>
<p>防抖值: {debounced}</p>
</div>
);
}
// 自定义防抖Hook
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// 使用防抖Hook
function SearchForm() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedTerm = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedTerm) {
console.log('执行搜索:', debouncedTerm);
}
}, [debouncedTerm]);
return (
<input
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
placeholder="搜索..."
/>
);
}第五部分:复杂表单管理
5.1 向导式表单(多步骤)
jsx
function WizardForm() {
const [step, setStep] = useState(1);
const [formData, setFormData] = useState({
// Step 1
username: '',
email: '',
password: '',
// Step 2
firstName: '',
lastName: '',
phone: '',
// Step 3
address: '',
city: '',
zipCode: '',
// Step 4
cardNumber: '',
expiryDate: '',
cvv: ''
});
const updateFormData = (updates) => {
setFormData(prev => ({
...prev,
...updates
}));
};
const nextStep = () => {
if (validateStep(step)) {
setStep(s => Math.min(s + 1, 4));
}
};
const prevStep = () => {
setStep(s => Math.max(s - 1, 1));
};
const validateStep = (stepNumber) => {
// 根据步骤验证不同字段
switch (stepNumber) {
case 1:
return formData.username && formData.email && formData.password;
case 2:
return formData.firstName && formData.lastName && formData.phone;
case 3:
return formData.address && formData.city && formData.zipCode;
case 4:
return formData.cardNumber && formData.expiryDate && formData.cvv;
default:
return true;
}
};
const handleSubmit = (e) => {
e.preventDefault();
if (step === 4 && validateStep(4)) {
console.log('提交完整数据:', formData);
alert('注册成功!');
}
};
const renderStep = () => {
switch (step) {
case 1:
return <AccountStep data={formData} onChange={updateFormData} />;
case 2:
return <PersonalStep data={formData} onChange={updateFormData} />;
case 3:
return <AddressStep data={formData} onChange={updateFormData} />;
case 4:
return <PaymentStep data={formData} onChange={updateFormData} />;
default:
return null;
}
};
return (
<div className="wizard-form">
<div className="progress-steps">
{[1, 2, 3, 4].map(s => (
<div key={s} className={`step ${step >= s ? 'active' : ''}`}>
{s}. {['账号', '个人', '地址', '支付'][s - 1]}
</div>
))}
</div>
<form onSubmit={handleSubmit}>
{renderStep()}
<div className="form-navigation">
{step > 1 && (
<button type="button" onClick={prevStep}>
上一步
</button>
)}
{step < 4 ? (
<button type="button" onClick={nextStep}>
下一步
</button>
) : (
<button type="submit">
提交
</button>
)}
</div>
</form>
</div>
);
}
// 步骤组件
function AccountStep({ data, onChange }) {
return (
<div>
<h3>账号信息</h3>
<input
value={data.username}
onChange={e => onChange({ username: e.target.value })}
placeholder="用户名"
/>
<input
type="email"
value={data.email}
onChange={e => onChange({ email: e.target.value })}
placeholder="邮箱"
/>
<input
type="password"
value={data.password}
onChange={e => onChange({ password: e.target.value })}
placeholder="密码"
/>
</div>
);
}5.2 条件表单字段
jsx
function ConditionalFields() {
const [formData, setFormData] = useState({
userType: 'individual', // individual or company
name: '',
companyName: '',
taxId: '',
email: '',
phone: ''
});
const handleChange = (field) => (e) => {
setFormData(prev => ({
...prev,
[field]: e.target.value
}));
};
const isCompany = formData.userType === 'company';
return (
<form>
<div>
<label>用户类型:</label>
<select value={formData.userType} onChange={handleChange('userType')}>
<option value="individual">个人</option>
<option value="company">企业</option>
</select>
</div>
{!isCompany && (
<div>
<label>姓名:</label>
<input
value={formData.name}
onChange={handleChange('name')}
placeholder="请输入姓名"
/>
</div>
)}
{isCompany && (
<>
<div>
<label>公司名称:</label>
<input
value={formData.companyName}
onChange={handleChange('companyName')}
placeholder="请输入公司名称"
/>
</div>
<div>
<label>税号:</label>
<input
value={formData.taxId}
onChange={handleChange('taxId')}
placeholder="请输入税号"
/>
</div>
</>
)}
<div>
<label>邮箱:</label>
<input
type="email"
value={formData.email}
onChange={handleChange('email')}
placeholder="邮箱"
/>
</div>
<div>
<label>电话:</label>
<input
type="tel"
value={formData.phone}
onChange={handleChange('phone')}
placeholder="电话"
/>
</div>
<button type="submit">提交</button>
</form>
);
}第六部分:实战案例
6.1 完整的注册表单
jsx
function RegistrationForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: '',
confirmPassword: '',
firstName: '',
lastName: '',
birthday: '',
gender: '',
country: '',
agreeToTerms: false
});
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const [submitting, setSubmitting] = useState(false);
const handleChange = useCallback((field) => (e) => {
const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
setFormData(prev => ({
...prev,
[field]: value
}));
// 清除该字段的错误
if (errors[field]) {
setErrors(prev => {
const newErrors = { ...prev };
delete newErrors[field];
return newErrors;
});
}
}, [errors]);
const handleSubmit = async (e) => {
e.preventDefault();
// 验证
const newErrors = validateForm(formData);
setErrors(newErrors);
if (Object.keys(newErrors).length > 0) {
return;
}
setSubmitting(true);
try {
const response = await fetch('/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
if (response.ok) {
alert('注册成功!');
} else {
const data = await response.json();
setErrors({ submit: data.message });
}
} catch (error) {
setErrors({ submit: '注册失败,请重试' });
} finally {
setSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit} className="registration-form">
<h2>注册账号</h2>
{errors.submit && (
<div className="error-banner">{errors.submit}</div>
)}
<section>
<h3>账号信息</h3>
<FormField
label="用户名"
value={formData.username}
onChange={handleChange('username')}
error={errors.username}
/>
<FormField
label="邮箱"
type="email"
value={formData.email}
onChange={handleChange('email')}
error={errors.email}
/>
<FormField
label="密码"
type="password"
value={formData.password}
onChange={handleChange('password')}
error={errors.password}
/>
<FormField
label="确认密码"
type="password"
value={formData.confirmPassword}
onChange={handleChange('confirmPassword')}
error={errors.confirmPassword}
/>
</section>
<button type="submit" disabled={submitting || !formData.agreeToTerms}>
{submitting ? '提交中...' : '注册'}
</button>
</form>
);
}
function validateForm(data) {
const errors = {};
if (!data.username) errors.username = '用户名不能为空';
if (!data.email) errors.email = '邮箱不能为空';
if (!data.password) errors.password = '密码不能为空';
if (data.password !== data.confirmPassword) errors.confirmPassword = '密码不一致';
if (!data.agreeToTerms) errors.agreeToTerms = '必须同意条款';
return errors;
}练习题
基础练习
- 创建一个包含10个字段的表单
- 实现通用的change处理器
- 添加表单验证
进阶练习
- 实现动态表单字段添加/删除
- 创建表单数组管理(如联系人列表)
- 优化大型表单性能
高级练习
- 实现复杂的表单验证系统
- 创建表单构建器
- 使用React 19特性优化表单
通过本章学习,你已经掌握了多个表单元素的处理技巧。这是构建复杂表单应用的基础。继续学习,成为表单大师!