You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 01:28:13