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>
  );
}

注意看,selectvalue 绑定了 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 非受控,取文件对象数组

嗯,掌握了这些,你就能轻松应对大部分表单场景了。下一章咱们聊聊表单验证,那才是真正考验功力的地方。