先记住这个答案
React 的受控多选 select 应设置 multiple,并把选中项 value 的字符串数组传给 select 的 value。在 onChange 中遍历 event.target.selectedOptions,取得所有当前选中项的 value 后更新状态。不要只读取 target.value,也不要给每个 option 设置 selected 来维护另一套状态。提交原生表单时,多选会产生同名字段的多个值,应使用 FormData.getAll 读取。
- 多选 value 使用字符串数组,空数组表示不选
- selectedOptions 提供当前所有选中项
- 重复字段用 getAll,普通对象转换可能丢失多值
多选为什么不能只读取 target.value
单选中读取 value 通常足以描述选择,多选则需要一个集合。只读取 select.value 无法表达全部选中项,可能把用户选择的其它项目遗漏。可以通过 Array.from(event.target.selectedOptions, option => option.value) 收集当前 DOM 选择,再保存为新的状态数组。
React 的控制入口放在 select 上,而不是让每个 option 独立设置 selected。这样当前选择只有一个权威来源。某些组件库的多选 API 可能返回对象或数字,但不能直接套用到原生 select;原生 option 的值按字符串处理,应在边界统一类型。
完整示例使用相同 name 提交多个值
下面使用标签数组控制技术方向选择,并在提交时通过 FormData.getAll 读取全部值。清空按钮直接把受控数组设为空。示例区分了当前选择与提交结果,避免把一个值误当成多选集合,也没有执行网络请求。
原生多选的具体操作方式由浏览器与系统决定,桌面可能涉及修饰键,移动端也有不同选择界面。产品需要更明确的勾选体验时,可以评估复选框组或经过无障碍验证的组件,而不能为了视觉统一随意替换原生交互却丢失键盘能力。
import { useState } from 'react';
export default function SkillsForm() {
const [selected, setSelected] = useState(['react']);
const [submitted, setSubmitted] = useState('尚未提交');
return (
<form onSubmit={event => {
event.preventDefault();
const values = new FormData(event.currentTarget).getAll('skills');
setSubmitted(values.join(', ') || '没有选择');
}}>
<label>技术方向<select name="skills" multiple value={selected}
onChange={event => setSelected(Array.from(event.target.selectedOptions, option => option.value))}>
<option value="react">React</option>
<option value="vue">Vue</option>
<option value="node">Node.js</option>
</select></label>
<button type="button" onClick={() => setSelected([])}>清空选择</button>
<button type="submit">提交方向</button>
<p aria-live="polite">{submitted}</p>
</form>
);
}初始选择 React,可改为多个选项再提交,结果区域显示全部表单值。清空选择后提交显示没有选择。示例没有控制台输出,也不向外部发送数据。
选项变化时不要默默丢失业务含义
选项来自异步接口时,应定义已经选择但暂时未加载或已失效的值如何处理。可按业务要求保留并提示、等待选项加载,或明确移除失效项。不能只看 DOM 没有显示某个选项,就假定 state 中的选择也已经自动清理。
选择数组通常表示集合,而不是用户点击的先后顺序。selectedOptions 与表单读取不能代替专门的顺序模型;如果业务要求用户安排优先级,应另行保存并提供排序交互。提交校验还应检查值是否属于当前允许集合,前端 option 并不是服务端可接受输入的唯一来源。
容易答错的地方
- multiple 只是允许多点几个,value 仍传一个字符串
- 受控多选需要数组表达所有选择。使用单个字符串会让类型与控件模式不匹配,也无法完整表达选中集合。
- Object.fromEntries(new FormData(form)) 能保留所有同名选项
- 普通对象的同名属性会互相覆盖,不能表达重复字段列表。多选字段应使用 getAll,或显式构建支持多值的数据结构。
面试官还会怎么问?
defaultValue 在多选模式下也传数组吗?
是,它用数组提供初始选择,但不会变成持续控制当前选择的入口。需要外部状态驱动时使用 value,并保持受控模式一致。
想表示没有选择时可以传 undefined 吗?
受控多选应使用空数组表达空集合。undefined 容易使控制模式发生变化,空选择与尚未提供受控值是不同含义。
选项 value 重复会怎样?
重复值不能稳定区分业务项,受控数组也无法表达只选其中某一个重复值的意图。应为不同选项使用唯一且稳定的值,展示文字可以重复或本地化。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。