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

React+Material UI列表:实现单个文件夹StarBorder图标独立切换

解决Material UI嵌套列表单个子项星标独立控制问题

问题描述

开发React+Material UI嵌套列表组件时,点击子文件夹希望仅当前项显示StarBorder图标,再次点击隐藏,但目前所有子项会同步显示/隐藏图标。

问题根源

当前使用单个布尔值toggleSiteCheckmark作为所有子项的状态控制变量,所有子项共享同一个状态,因此点击任意子项都会触发全局状态变化,导致所有子项的图标同步切换。

解决方案

方案1:用对象存储每个子项的独立状态

将状态改为对象结构,以子项的唯一标识(比如名称)作为键,布尔值作为值,点击时仅切换对应子项的状态。

修改后的完整代码:

import React, { useState } from "react";
import ListSubheader from "@mui/material/ListSubheader";
import List from "@mui/material/List";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import Collapse from "@mui/material/Collapse";
import InboxIcon from "@mui/icons-material/MoveToInbox";
import ExpandLess from "@mui/icons-material/ExpandLess";
import ExpandMore from "@mui/icons-material/ExpandMore";
import StarBorder from "@mui/icons-material/StarBorder";

export default function NestedList() {
  const [open, setOpen] = useState(true);
  // 初始化所有子项的星标状态为未选中
  const [starredItems, setStarredItems] = useState({
    Inbox: false,
    "Sent Items": false,
    "Deleted Items": false,
    Archieve: false
  });

  const handleClick = () => {
    setOpen(!open);
  };

  const handleSelected = (itemName) => {
    // 解构原有状态,仅切换当前点击项的状态
    setStarredItems(prevState => ({
      ...prevState,
      [itemName]: !prevState[itemName]
    }));
  };

  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">
          Nested List Items
        </ListSubheader>
      }
    >
      <ListItemButton onClick={handleClick}>
        <ListItemIcon>
          <InboxIcon />
        </ListItemIcon>
        <ListItemText primary="Folder" />
        {open ? <ExpandLess /> : <ExpandMore />}
      </ListItemButton>
      <Collapse in={open} timeout="auto" unmountOnExit>
        <List component="div" disablePadding>
          <ListItemButton onClick={() => handleSelected("Inbox")} sx={{ pl: 4 }}>
            <ListItemIcon>{starredItems.Inbox && <StarBorder />}</ListItemIcon>
            <ListItemText primary="Inbox" />
          </ListItemButton>
          <ListItemButton onClick={() => handleSelected("Sent Items")} sx={{ pl: 4 }}>
            <ListItemIcon>{starredItems["Sent Items"] && <StarBorder />}</ListItemIcon>
            <ListItemText primary="Sent Items" />
          </ListItemButton>
          <ListItemButton onClick={() => handleSelected("Deleted Items")} sx={{ pl: 4 }}>
            <ListItemIcon>{starredItems["Deleted Items"] && <StarBorder />}</ListItemIcon>
            <ListItemText primary="Deleted Items" />
          </ListItemButton>
          <ListItemButton onClick={() => handleSelected("Archieve")} sx={{ pl: 4 }}>
            <ListItemIcon>{starredItems.Archieve && <StarBorder />}</ListItemIcon>
            <ListItemText primary="Archieve" />
          </ListItemButton>
        </List>
      </Collapse>
    </List>
  );
}

方案2:封装独立子项组件

把单个可星标的子项抽成独立组件,每个组件自己维护星标状态,实现完全独立的控制逻辑。

修改后的完整代码:

import React, { useState } from "react";
import ListSubheader from "@mui/material/ListSubheader";
import List from "@mui/material/List";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import Collapse from "@mui/material/Collapse";
import InboxIcon from "@mui/icons-material/MoveToInbox";
import ExpandLess from "@mui/icons-material/ExpandLess";
import ExpandMore from "@mui/icons-material/ExpandMore";
import StarBorder from "@mui/icons-material/StarBorder";

// 独立子项组件,自身维护星标状态
const StarrableListItem = ({ primaryText }) => {
  const [isStarred, setIsStarred] = useState(false);
  
  const handleToggle = () => {
    setIsStarred(!isStarred);
  };
  
  return (
    <ListItemButton onClick={handleToggle} sx={{ pl: 4 }}>
      <ListItemIcon>{isStarred && <StarBorder />}</ListItemIcon>
      <ListItemText primary={primaryText} />
    </ListItemButton>
  );
};

export default function NestedList() {
  const [open, setOpen] = useState(true);

  const handleClick = () => {
    setOpen(!open);
  };

  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">
          Nested List Items
        </ListSubheader>
      }
    >
      <ListItemButton onClick={handleClick}>
        <ListItemIcon>
          <InboxIcon />
        </ListItemIcon>
        <ListItemText primary="Folder" />
        {open ? <ExpandLess /> : <ExpandMore />}
      </ListItemButton>
      <Collapse in={open} timeout="auto" unmountOnExit>
        <List component="div" disablePadding>
          <StarrableListItem primaryText="Inbox" />
          <StarrableListItem primaryText="Sent Items" />
          <StarrableListItem primaryText="Deleted Items" />
          <StarrableListItem primaryText="Archieve" />
        </List>
      </Collapse>
    </List>
  );
}

内容的提问来源于stack exchange,提问作者walee

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 17:15:47