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
相关产品推荐
相关产品推荐

