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

TypeScript实现点击箭头展开折叠嵌套表格问题求解

问题修复方案

你的代码存在4个核心问题导致嵌套表格无法正常渲染、交互异常:

  • 行切换逻辑错误:toggleRow 固定将行状态设为true,无法实现点击折叠
  • 箭头图标判断逻辑错误:直接判断openRows对象(永远为真),没有读取对应行的展开状态
  • 表格DOM结构违规:将折叠内容、明细单元格直接塞在箭头列的TableCell内部,不符合HTML表格嵌套规范,也没有渲染主行的姓名基础字段
  • 嵌套表格未做列跨域处理:展开的明细行需要占满整行宽度,必须设置colSpan匹配总列数

修复后完整代码

import { useState } from 'react';
import {
  Paper,
  TableContainer,
  Table,
  TableHead,
  TableRow,
  TableCell,
  Typography,
  TableBody,
  IconButton,
  Collapse,
  Box,
} from '@mui/material';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import ArrowDropUpIcon from '@mui/icons-material/ArrowDropUp';

// 原有类型定义保留
export interface UserData {
  userId: string;
  firstName: string;
  lastName: string;
  jobDetails: {
    company: string;
    title: string;
  };
}

interface UserDataProps {
  data: UserData[];
  classes: {
    container: string;
    tableRoot: string;
    cell: string;
    tableRow: string;
    hover: string;
    arrowCell: string;
    nestedTable: string;
  };
}

const UserTable = ({ data, classes }: UserDataProps) => {
  const columns = ["First Name", "Last Name"];
  const [openRows, setOpenRows] = useState<Record<string, boolean>>({});

  // 修复切换逻辑:点击时取反当前行状态
  const toggleRow = (userId: string) =>
    setOpenRows((prevState) => ({
      ...prevState,
      [userId]: !prevState[userId],
    }));

  return (
    <Paper elevation={3} variant="outlined">
      <TableContainer className={classes.container}>
        <Table stickyHeader classes={{ root: classes.tableRoot }}>
          <colgroup>
            <col width="5%" />
            <col width="10%" />
            <col width="15%" />
          </colgroup>
          <TableHead>
            <TableRow>
              {/* 单独给箭头控制列预留表头位置,避免列数错位 */}
              <TableCell className={classes.cell} role="columnheader" />
              {columns.map((column) => (
                <TableCell
                  key={column}
                  className={classes.cell}
                  role="columnheader"
                >
                  <Typography variant="h6">{column}</Typography>     
                </TableCell>
              ))}
            </TableRow>
          </TableHead>
          <TableBody>
            {data.map((dataRow) => {
              const isRowOpen = !!openRows[dataRow.userId];
              return (
                <>
                  {/* 主数据行 */}
                  <TableRow
                    key={dataRow.userId}
                    title="tableRow"
                    className={classes.tableRow}
                    classes={{ hover: classes.hover }}
                    hover
                  >
                    <TableCell classes={{ root: classes.arrowCell }}>
                      <IconButton
                        size="small"
                        onClick={() => toggleRow(dataRow.userId)}
                      >
                        {/* 修复箭头判断:展开显示向下箭头,折叠显示向上箭头 */}
                        {isRowOpen ? <ArrowDropDownIcon /> : <ArrowDropUpIcon />}
                      </IconButton>
                    </TableCell>
                    {/* 补全主行缺失的基础数据单元格 */}
                    <TableCell>{dataRow.firstName}</TableCell>
                    <TableCell>{dataRow.lastName}</TableCell>
                  </TableRow>
                  {/* 展开明细行:独立TableRow,colSpan=3占满整行宽度 */}
                  <TableRow>
                    <TableCell colSpan={3} style={{ paddingBottom: 0, paddingTop: 0 }}>
                      <Collapse
                        in={isRowOpen}
                        timeout="auto"
                        unmountOnExit
                      >
                        <Box margin={1}>
                          {/* 嵌套独立表格渲染明细,符合表格结构规范 */}
                          <Table size="small" className={classes.nestedTable}>
                            <TableHead>
                              <TableRow>
                                <TableCell>公司</TableCell>
                                <TableCell>职位</TableCell>
                              </TableRow>
                            </TableHead>
                            <TableBody>
                              <TableRow>
                                <TableCell>{dataRow.jobDetails.company}</TableCell>
                                <TableCell>{dataRow.jobDetails.title}</TableCell>
                              </TableRow>
                            </TableBody>
                          </Table>
                        </Box>
                      </Collapse>
                    </TableCell>
                  </TableRow>
                </>
              );
            })}
          </TableBody>
        </Table>
      </TableContainer>
    </Paper>
  );
};

export default UserTable;

关键改动说明

  • 调整表头结构,单独为箭头控制列预留位置,避免列数不对齐
  • 每行拆分为主数据行、明细行两个独立TableRow,明细行通过colSpan占满所有列宽度,不破坏表格布局
  • 嵌套明细使用独立Table组件渲染,符合HTML表格嵌套规则,不会出现单元格错位
  • 修复状态切换、箭头判断的逻辑错误,点击可正常展开/折叠
  • 补全主行原本缺失的姓名字段渲染

如果明细是多行列表,直接在嵌套表格的TableBody中循环渲染对应TableRow即可,逻辑和主表一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 08:42:25