4、表单元素详解:处理input、textarea、select、checkbox、radio等各类表单元素
好,咱们进入实战环节。表单里那些常见的元素——input、textarea、select、checkbox、radio,你天天用,但你真的把它们都「伺候」明白了吗?
我个人习惯,每次接手新项目,第一件事就是看看表单怎么写的。因为表单是用户和系统交互最频繁的地方,也是最容易出幺蛾子的地方。今天咱们就把这些元素挨个捋一遍,看看在 React 里怎么优雅地处理它们。
4.1 文本输入:input[type="text"] 与 textarea
先说最基础的。input 和 textarea,说白了就是让用户打字的地方。但在 React 里,它们有个共同点——都得受控。
核心原则:React 推崇「单向数据流」。输入框的值由 state 驱动,而不是由 DOM 自己说了算。
看个例子:
function TextInput() {
const [name, setName] = useState('');
const handleChange = (e) => {
setName(e.target.value);
};
return <input type="text" value={name} onChange={handleChange} />;
}
嗯,这里要注意:value 绑定了 state,onChange 更新 state。这样 input 就完全在你的掌控之中了。
textarea 呢?在 HTML 里它是双标签,内容写在标签之间。但在 React 里,它和 input 一样,用 value 属性:
function TextAreaInput() {
const [bio, setBio] = useState('');
return (
<textarea
value={bio}
onChange={(e) => setBio(e.target.value)}
rows={4}
/>
);
}
小技巧:我在项目中遇到过用户粘贴带格式的文本,导致页面布局错乱。后来我加了个 onPaste 事件,过滤掉富文本格式,只保留纯文本。你可以试试:e.clipboardData.getData('text/plain')。
4.2 选择框:select 与 option
下拉选择框,React 处理起来也很直接。不过有个坑,我当年踩过。
function SelectExample() {
const [city, setCity] = useState('');
const handleChange = (e) => {
setCity(e.target.value);
};
return (
<select value={city} onChange={handleChange}>
<option value="">请选择城市</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="shenzhen">深圳</option>
</select>
);
}
注意看,select 的 value 绑定了 state,onChange 更新 state。这和 input 如出一辙。
避坑指南:我曾经在项目里用 defaultValue 代替 value,结果发现下拉框的值死活不更新。后来才意识到,defaultValue 只在组件首次渲染时生效,后续 state 变化它根本不理会。所以,受控组件一定要用 value。
多选呢?加上 multiple 属性即可。但取值时要注意,e.target.value 只会返回第一个选中的值。你需要遍历 e.target.options:
const handleMultiChange = (e) => {
const selectedValues = Array.from(e.target.options)
.filter(option => option.selected)
.map(option => option.value);
setSelectedCities(selectedValues);
};
4.3 复选框:checkbox
复选框分两种:单个开关和多个选项组。
单个 checkbox:说白了就是一个布尔值。
function SingleCheckbox() {
const [agree, setAgree] = useState(false);
const handleChange = (e) => {
setAgree(e.target.checked); // 注意是 checked,不是 value
};
return (
<label>
<input type="checkbox" checked={agree} onChange={handleChange} />
我同意用户协议
</label>
);
}
你想想看,这里用的是 e.target.checked,而不是 e.target.value。因为 checkbox 的状态是「选中」或「未选中」,而不是一个字符串值。
多个 checkbox 组:通常用一个数组来管理。
function CheckboxGroup() {
const [hobbies, setHobbies] = useState([]);
const handleChange = (e) => {
const value = e.target.value;
const isChecked = e.target.checked;
if (isChecked) {
setHobbies([...hobbies, value]);
} else {
setHobbies(hobbies.filter(item => item !== value));
}
};
return (
<div>
<label>
<input type="checkbox" value="reading" onChange={handleChange} />
阅读
</label>
<label>
<input type="checkbox" value="coding" onChange={handleChange} />
编程
</label>
<label>
<input type="checkbox" value="gaming" onChange={handleChange} />
游戏
</label>
</div>
);
}
经验之谈:我在项目中遇到过用户快速点击复选框,导致 state 更新不及时的问题。后来我用 useReducer 替代了 useState,把更新逻辑集中管理,问题就解决了。如果你的表单选项很多,可以考虑这个方案。
4.4 单选按钮:radio
radio 和 checkbox 不同,它是「多选一」。同一组 radio 共享一个 name 属性。
function RadioGroup() {
const [gender, setGender] = useState('');
const handleChange = (e) => {
setGender(e.target.value);
};
return (
<div>
<label>
<input type="radio" name="gender" value="male" onChange={handleChange} />
男
</label>
<label>
<input type="radio" name="gender" value="female" onChange={handleChange} />
女
</label>
</div>
);
}
注意,这里每个 radio 的 name 必须相同,才能实现互斥。而 value 则是每个选项的唯一标识。
关键点:radio 的选中状态由 checked 属性控制,但通常我们不需要手动设置 checked。因为同一 name 的 radio 会自动互斥,你只需要通过 value 来获取选中的值即可。
4.5 文件上传:input[type="file"]
文件上传是个特殊的存在。它是个「非受控组件」,因为文件路径是浏览器安全机制保护的,React 无法直接控制。
function FileUpload() {
const [file, setFile] = useState(null);
const handleChange = (e) => {
const selectedFile = e.target.files[0];
setFile(selectedFile);
};
return (
<div>
<input type="file" onChange={handleChange} />
{file && <p>已选择:{file.name}</p>}
</div>
);
}
这里没有 value 属性,因为文件路径是只读的。我们通过 e.target.files 获取文件对象。
避坑指南:我曾经在项目里想重置文件输入框,直接设置 value={null},结果报错了。正确的做法是操作 DOM:e.target.value = null。或者用 ref 来清空。
4.6 统一处理:一个 onChange 搞定所有
你想想看,如果每个表单元素都写一个单独的 handleChange,那代码得多冗余?我习惯用一个通用的 handleChange 来处理所有类型的输入:
function GenericForm() {
const [formData, setFormData] = useState({
name: '',
bio: '',
city: '',
agree: false,
gender: ''
});
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
};
return (
<form>
<input name="name" value={formData.name} onChange={handleChange} />
<textarea name="bio" value={formData.bio} onChange={handleChange} />
<select name="city" value={formData.city} onChange={handleChange}>
<option value="">请选择</option>
<option value="beijing">北京</option>
</select>
<input type="checkbox" name="agree" checked={formData.agree} onChange={handleChange} />
<input type="radio" name="gender" value="male" onChange={handleChange} />
<input type="radio" name="gender" value="female" onChange={handleChange} />
</form>
);
}
看到了吗?核心就是利用 e.target.name 来动态更新 state 中对应的字段。对于 checkbox,特殊处理一下 checked 属性。其他类型,直接用 value。
个人习惯:我一般会在 handleChange 里加个 console.log 调试信息,方便排查问题。上线前再删掉。另外,对于数字输入框,我还会加个 type === 'number' 的判断,把 value 转成数字:value ? Number(value) : ''。
4.7 总结一下
好了,咱们把常见的表单元素都过了一遍。说白了,React 里的表单处理就一个核心思想:受控组件。让 state 成为唯一数据源,通过 onChange 来更新 state。
不同类型的元素,取值方式略有不同:
| 元素类型 | 取值属性 | 说明 |
|---|---|---|
| input[type="text"] | e.target.value | 最常用,直接取字符串 |
| textarea | e.target.value | 和 input 一样,用 value 属性 |
| select | e.target.value | 单选直接取,多选需遍历 options |
| checkbox | e.target.checked | 布尔值,注意不是 value |
| radio | e.target.value | 同一 name 互斥,取选中的 value |
| input[type="file"] | e.target.files | 非受控,取文件对象数组 |
嗯,掌握了这些,你就能轻松应对大部分表单场景了。下一章咱们聊聊表单验证,那才是真正考验功力的地方。