ReactJS嵌套列表问题:展开单个分类时所有子列表均打开
React嵌套列表展开异常的解决方案
问题根源确实是你推测的那样:单个布尔状态open被所有可展开列表项共享,导致点击任何一个分类时,全局状态切换会触发所有绑定该状态的Collapse组件同步展开/折叠。
下面提供两种实用的解决思路,对应不同的场景需求:
思路1:用单个值记录当前展开的列表项ID(适合单开场景)
如果希望同一时间只能展开一个分类,用字符串/数值类型的状态记录当前展开项的唯一标识,而非布尔值。
修改后的代码示例:
import * as React from 'react'; import ListSubheader from '@mui/material/ListSubheader'; import List from '@mui/material/List'; import ListItemButton from '@mui/material/ListItemButton'; import ListItemText from '@mui/material/ListItemText'; import Collapse from '@mui/material/Collapse'; import ExpandLess from '@mui/icons-material/ExpandLess'; import ExpandMore from '@mui/icons-material/ExpandMore'; export default function SideBar() { // 初始为null,表示没有展开项;展开时存储对应课程的唯一标识 const [openId, setOpenId] = React.useState(null); // 点击时切换当前展开项的ID:如果是当前展开项则关闭,否则切换为该项ID const handleClick = (id) => { setOpenId(prevId => prevId === id ? null : id); }; return ( <List sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }} component="nav" aria-labelledby="nested-list-subheader" subheader={ <ListSubheader component="div" id="nested-list-subheader"> courses </ListSubheader> } > <ListItemButton onClick={() => handleClick('course1')}> <ListItemText primary="Course 1" /> {openId === 'course1' ? <ExpandLess /> : <ExpandMore />} </ListItemButton> <Collapse in={openId === 'course1'} timeout="auto" unmountOnExit> <List component="div" disablePadding> <ListItemButton sx={{ pl: 4 }}> <ListItemText primary="Research Paper" /> </ListItemButton> </List> </Collapse> <ListItemButton> <ListItemText primary="Course 2" /> {/* Course2没有子列表,可移除图标或保持静态显示 */} </ListItemButton> <ListItemButton onClick={() => handleClick('course3')}> <ListItemText primary="Course 3" /> {openId === 'course3' ? <ExpandLess /> : <ExpandMore />} </ListItemButton> <Collapse in={openId === 'course3'} timeout="auto" unmountOnExit> <List component="div" disablePadding> <ListItemButton sx={{ pl: 4 }}> <ListItemText primary="Exams" /> </ListItemButton> </List> </Collapse> </List> ); }
思路2:用对象状态管理多个独立的展开状态(支持多开)
如果需要同时展开多个分类,用对象存储每个可展开项的状态,点击时单独切换对应项的布尔值。
修改后的代码示例:
import * as React from 'react'; import ListSubheader from '@mui/material/ListSubheader'; import List from '@mui/material/List'; import ListItemButton from '@mui/material/ListItemButton'; import ListItemText from '@mui/material/ListItemText'; import Collapse from '@mui/material/Collapse'; import ExpandLess from '@mui/icons-material/ExpandLess'; import ExpandMore from '@mui/icons-material/ExpandMore'; export default function SideBar() { // 对象的key是课程ID,value是展开状态(布尔值) const [openStates, setOpenStates] = React.useState({ course1: false, course3: false }); // 点击时切换对应课程的展开状态 const handleClick = (id) => { setOpenStates(prevStates => ({ ...prevStates, [id]: !prevStates[id] })); }; return ( <List sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }} component="nav" aria-labelledby="nested-list-subheader" subheader={ <ListSubheader component="div" id="nested-list-subheader"> courses </ListSubheader> } > <ListItemButton onClick={() => handleClick('course1')}> <ListItemText primary="Course 1" /> {openStates.course1 ? <ExpandLess /> : <ExpandMore />} </ListItemButton> <Collapse in={openStates.course1} timeout="auto" unmountOnExit> <List component="div" disablePadding> <ListItemButton sx={{ pl: 4 }}> <ListItemText primary="Research Paper" /> </ListItemButton> </List> </Collapse> <ListItemButton> <ListItemText primary="Course 2" /> </ListItemButton> <ListItemButton onClick={() => handleClick('course3')}> <ListItemText primary="Course 3" /> {openStates.course3 ? <ExpandLess /> : <ExpandMore />} </ListItemButton> <Collapse in={openStates.course3} timeout="auto" unmountOnExit> <List component="div" disablePadding> <ListItemButton sx={{ pl: 4 }}> <ListItemText primary="Exams" /> </ListItemButton> </List> </Collapse> </List> ); }
关键说明
- 两种思路的核心都是给每个可展开项分配独立的状态标识,避免全局状态污染。
- 思路1适合「只能展开一个分类」的手风琴效果场景,思路2支持「同时展开多个分类」的需求。
内容的提问来源于stack exchange,提问作者Jose Rangel
相关产品推荐
相关产品推荐

