如何在MUI的Select组件中嵌套div使用MenuItem而非直接子元素?
解决MUI Select带勾选+自定义头部/底部的问题
问题根源
- MUI Select组件要求
MenuItem必须是它的直接子元素,嵌套div会破坏组件内部的事件传递和值关联逻辑,导致无法正确识别选项 - 点击复选框时
event.target.value为undefined,是因为嵌套结构让事件无法正确传递到Select组件,无法获取到MenuItem的value值
两种可行解决方案
方案一:拆分Select与Menu,完全自定义下拉面板
直接用Select触发Menu弹出,手动控制Menu的显示隐藏,这样可以自由添加头部、底部,同时保证MenuItem的层级正确:
import { useState } from 'react'; import { Select, OutlinedInput, Menu, MenuItem, Checkbox, ListItemText, Typography, Box } from '@mui/material'; export default function CustomSelectWithHeaderFooter({ dataArray }) { const [personName, setPersonName] = useState([]); const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); const handleOpen = (event) => { setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; const handleChange = (name) => { const currentIndex = personName.indexOf(name); const newSelected = [...personName]; if (currentIndex === -1) { newSelected.push(name); } else { newSelected.splice(currentIndex, 1); } setPersonName(newSelected); }; return ( <div> <Select labelId="demo-multiple-checkbox-label" id="demo-multiple-checkbox" multiple value={personName} open={open} onOpen={handleOpen} onClose={handleClose} input={<OutlinedInput label="Tag" />} renderValue={(selected) => selected.join(', ')} displayEmpty > {/* 这里留空,下拉面板用自定义Menu实现 */} </Select> <Menu anchorEl={anchorEl} open={open} onClose={handleClose} PaperProps={{ style: { maxHeight: 400, width: 250, }, }} > {/* 自定义头部 */} <Box px={2} py={1} borderBottom={1} borderColor="divider"> <Typography variant="subtitle2">Sticky header</Typography> </Box> {/* 选项列表 */} {dataArray.map((name) => ( <MenuItem key={name} onClick={() => handleChange(name)}> <Checkbox checked={personName.indexOf(name) > -1} /> <ListItemText primary={name} /> </MenuItem> ))} {/* 自定义底部 */} <Box px={2} py={1} borderTop={1} borderColor="divider"> <Typography variant="subtitle2">Sticky footer</Typography> </Box> </Menu> </div> ); }
方案二:通过Select的MenuProps自定义菜单内容(更简洁)
利用Select的MenuProps属性,直接给内部的Menu组件添加自定义头部和底部,同时保证MenuItem是Menu的直接子元素:
import { useState } from 'react'; import { Select, OutlinedInput, MenuItem, Checkbox, ListItemText, Typography, Box } from '@mui/material'; export default function CustomSelectWithHeaderFooter({ dataArray }) { const [personName, setPersonName] = useState([]); const handleChange = (event) => { // 手动处理多选逻辑,自定义结构需自行维护选中数组 const { value } = event.target; setPersonName(typeof value === 'string' ? value.split(',') : value); }; return ( <Select labelId="demo-multiple-checkbox-label" id="demo-multiple-checkbox" multiple value={personName} onChange={handleChange} input={<OutlinedInput label="Tag" />} renderValue={(selected) => selected.join(', ')} MenuProps={{ PaperProps: { children: (props) => ( <div> {/* 自定义头部 */} <Box px={2} py={1} borderBottom={1} borderColor="divider"> <Typography variant="subtitle2">Sticky header</Typography> </Box> {/* 保留原Menu的子元素(即MenuItem列表) */} {props.children} {/* 自定义底部 */} <Box px={2} py={1} borderTop={1} borderColor="divider"> <Typography variant="subtitle2">Sticky footer</Typography> </Box> </div> ), style: { maxHeight: 400, width: 250, }, }, }} > {/* MenuItem直接作为Select的子元素,符合组件要求 */} {dataArray.map((name) => ( <MenuItem key={name} value={name}> <Checkbox checked={personName.indexOf(name) > -1} /> <ListItemText primary={name} /> </MenuItem> ))} </Select> ); }
关键说明
- 方案二的核心是通过
MenuProps.PaperProps.children注入自定义结构,同时保留原有的props.children(即Select渲染的MenuItem列表),既满足Select对MenuItem层级的要求,又能添加自定义头部底部 - 多选模式下,handleChange需要正确处理数组类型的value,避免出现undefined的情况
内容的提问来源于stack exchange,提问作者Exter
相关产品推荐
相关产品推荐

