MUI DataGrid实现每行独立展开/折叠行的问题求助
解决Material UI DataGrid每行独立展开/折叠的问题
问题现状
当前代码使用单个open布尔状态控制所有行的展开/折叠,导致点击任意行的图标时,所有行的折叠区域同步切换状态,无法实现每行独立控制。
解决方案
核心思路是用**集合(Set)**存储所有展开行的ID,替代单个布尔值,精准跟踪每一行的展开状态:
- 替换状态变量:将单个布尔状态改为存储展开行ID的Set
- 修改行内渲染逻辑:根据当前行ID判断是否处于展开状态
- 更新点击事件:点击图标时切换当前行ID在集合中的存在状态
具体修改步骤
1. 替换状态定义
把原来的单个布尔状态:
const [open, setOpen] = React.useState(false)
改为存储展开行ID的Set:
// 用Set存储展开的行ID,初始为空集合 const [expandedRows, setExpandedRows] = React.useState(new Set())
2. 修改cheks列的renderCell函数
接收当前行的row参数,根据row.name(你的getRowId用的是row.name)判断该行是否展开,并更新点击逻辑:
renderCell: ({ row }) => { // 判断当前行是否处于展开状态 const isOpen = expandedRows.has(row.name) // 切换当前行的展开状态 const handleToggle = () => { setExpandedRows(prev => { const newSet = new Set(prev) isOpen ? newSet.delete(row.name) : newSet.add(row.name) return newSet }) } return ( <Typography> <IconButton size='small' onClick={handleToggle}> {isOpen ? <RemoveIcon /> : <ControlPointIcon />} </IconButton> <Collapse in={isOpen} timeout='auto' unmountOnExit> <Box sx={{ margin: 1 }}> <Typography>{isOpen ? 1 : 2}</Typography> </Box> </Collapse> </Typography> ) },
修改后的完整代码
import * as React from 'react' import { Box, Typography } from '@mui/material' import { DataGrid, GridToolbar } from '@mui/x-data-grid' import { tokens } from '../../theme' import { GetApiServices } from '../../data/mociktData' import Header from '../../components/Header' import { useTheme } from '@mui/material' import { LineChart } from '@mui/x-charts/LineChart' import IconButton from '@mui/material/IconButton' import ControlPointIcon from '@mui/icons-material/ControlPoint' import RemoveIcon from '@mui/icons-material/Remove' import Collapse from '@mui/material/Collapse' const Monitoring = () => { const theme = useTheme() const colors = tokens(theme.palette.mode) const data = GetApiServices[0].data.result .filter(item => 'metric' in item) .map(item => { const ret = Object.fromEntries(Object.entries(item.metric)) ret['value'] = item.value[1] return ret }) const m = new Map() const r = [] for (const item of data) { const current = m.get(item.name) || 0 m.set(item.name, current + parseInt(item.value)) } for (let [key, value] of m.entries()) r.push({ name: key, value: value }) const pData = [2400, 1398, 9800, 3908, 4800, 3800, 4300] const xLabels = [ 'Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G', ] // 用Set存储展开的行ID const [expandedRows, setExpandedRows] = React.useState(new Set()) const columns = [ { field: 'cheks', headerName: '', width: 50, renderCell: ({ row }) => { const isOpen = expandedRows.has(row.name) const handleToggle = () => { setExpandedRows(prev => { const newSet = new Set(prev) isOpen ? newSet.delete(row.name) : newSet.add(row.name) return newSet }) } return ( <Typography> <IconButton size='small' onClick={handleToggle}> {isOpen ? <RemoveIcon /> : <ControlPointIcon />} </IconButton> <Collapse in={isOpen} timeout='auto' unmountOnExit> <Box sx={{ margin: 1 }}> <Typography>{isOpen ? 1 : 2}</Typography> </Box> </Collapse> </Typography> ) }, }, { field: 'name', headerName: 'Services', width: 180, cellClassName: 'name-column--cell', }, { field: 'day', headerName: 'Dashboards', flex: 1, margin: 1, headerAlign: 'center', align: 'center', width: 500, renderCell: () => { return ( <Box flex-direction='column'> <LineChart height={200} series={[{ data: pData, label: 'pv' }]} xAxis={[{ scaleType: 'point', data: xLabels }]} /> </Box> ) }, }, { field: 'access', headerName: 'Сейчас', width: 180, headerAlign: 'center', align: 'center', cellClassName: 'name-column--cell', renderCell: ({ row: { value } }) => { return ( <Box width='50%' m='0 auto' marginTop='80px' p='5px' display='flex' justifyContent='center' alignItems='center' backgroundColor={ value === 4 ? colors.greenAccent[700] : value === 3 ? colors.orange[500] : value === 2 ? colors.orange[500] : value === 1 ? colors.redAccent[700] : value === 0 ? colors.redAccent[700] : colors.orange[500] } borderRadius='4px' > <Typography color={colors.grey[100]} sx={{ ml: '5px' }}> {value === 4 ? 'Available' : value === 3 ? 'Minor' : value === 2 ? 'Minor' : value === 1 ? 'Warning' : value === 0 ? 'Offline' : 'wrong'} </Typography> </Box> ) }, }, ] return ( <Box m='20px'> <Header title='Status Of' subtitle='Something Info' /> <Box m='40px 0 0 0' height='75vh' sx={{ '& .MuiDataGrid-root': { border: 'none', }, '& .MuiDataGrid-cell': { borderBottom: 'none', }, '& .name-column--cell': { color: colors.greenAccent[300], }, '& .MuiDataGrid-columnHeaders': { backgroundColor: colors.blueAccent[700], borderBottom: 'none', }, '& .MuiDataGrid-virtualScroller': { backgroundColor: colors.primary[400], }, '& .MuiDataGrid-footerContainer': { borderTop: 'none', backgroundColor: colors.blueAccent[700], }, '& .MuiCheckbox-root': { color: `${colors.greenAccent[200]} !important`, }, '& .MuiDataGrid-toolbarContainer .MuiButton-text': { color: `${colors.grey[100]} !important`, }, }} > <DataGrid getRowHeight={() => 'auto'} rows={r} columns={columns} getRowId={row => row.name} components={{ Toolbar: GridToolbar }} /> </Box> </Box> ) } export default Monitoring
关键说明
- 用
Set存储展开行ID是因为它的添加/删除操作效率高,且能自动去重 - 依赖
getRowId返回的唯一标识(这里是row.name)来区分不同行,确保状态对应正确 - 每次更新状态时都创建新的
Set实例,保证React能检测到状态变化并重新渲染
内容的提问来源于stack exchange,提问作者Masklin
相关产品推荐
相关产品推荐

