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

MUI DataGrid实现每行独立展开/折叠行的问题求助

解决Material UI DataGrid每行独立展开/折叠的问题

问题现状

当前代码使用单个open布尔状态控制所有行的展开/折叠,导致点击任意行的图标时,所有行的折叠区域同步切换状态,无法实现每行独立控制。

解决方案

核心思路是用**集合(Set)**存储所有展开行的ID,替代单个布尔值,精准跟踪每一行的展开状态:

  1. 替换状态变量:将单个布尔状态改为存储展开行ID的Set
  2. 修改行内渲染逻辑:根据当前行ID判断是否处于展开状态
  3. 更新点击事件:点击图标时切换当前行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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 10:22:03