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

Typescript Material-UI Table因Data接口差异的重复代码复用问题求解

改造方案

1. 定义通用泛型类型

核心思路是利用TypeScript泛型让组件不绑定固定的数据接口,支持任意传入的数据结构类型。首先定义可复用的基础类型:

// 通用表头单元格类型,泛型T对应当前表格的数据结构
interface HeadCell<T extends object> {
  id: keyof T;
  label: string;
  numeric?: boolean;
  disablePadding?: boolean;
}

// 泛型化的EnhancedTableProps,不再绑定固定的Data接口
interface EnhancedTableProps<T extends object> {
  classes: ReturnType<typeof useStyles>;
  numSelected: number;
  onRequestSort: (event: React.MouseEvent<unknown>, property: keyof T) => void;
  onSelectAllClick: (event: React.ChangeEvent<HTMLInputElement>) => void;
  order: 'asc' | 'desc';
  orderBy: keyof T;
  rowCount: number;
  // 表头配置作为props传入,不同表格传专属配置即可
  headCells: HeadCell<T>[];
}

2. 改造EnhancedTableHead为泛型组件

原有渲染逻辑完全不需要修改,仅需要给组件加上泛型声明:

function EnhancedTableHead<T extends object>(props: EnhancedTableProps<T>) {
  const { classes, onSelectAllClick, order, orderBy, numSelected, rowCount, onRequestSort, headCells } = props;
  const createSortHandler = (property: keyof T) => (event: React.MouseEvent<unknown>) => {
    onRequestSort(event, property);
  };

  return (
    <TableHead>
      <TableRow>
        <TableCell padding="checkbox">
          <Checkbox
            indeterminate={numSelected > 0 && numSelected < rowCount}
            checked={rowCount > 0 && numSelected === rowCount}
            onChange={onSelectAllClick}
            inputProps={{ 'aria-label': '全选当前页项' }}
          />
        </TableCell>
        {headCells.map((headCell) => (
          <TableCell
            key={String(headCell.id)}
            align={headCell.numeric ? 'right' : 'left'}
            padding={headCell.disablePadding ? 'none' : 'normal'}
            sortDirection={orderBy === headCell.id ? order : false}
          >
            <TableSortLabel
              active={orderBy === headCell.id}
              direction={orderBy === headCell.id ? order : 'asc'}
              onClick={createSortHandler(headCell.id)}
            >
              {headCell.label}
              {orderBy === headCell.id ? (
                <span className={classes.visuallyHidden}>
                  {order === 'desc' ? '已降序排序' : '已升序排序'}
                </span>
              ) : null}
            </TableSortLabel>
          </TableCell>
        ))}
      </TableRow>
    </TableHead>
  );
}

3. 业务场景使用示例

针对不同页面的不同数据结构,仅需要定义对应的数据接口和表头配置即可,组件逻辑100%复用:

// 示例:用户管理页的表格数据结构
interface UserData {
  id: number;
  name: string;
  email: string;
  createTime: string;
}

// 对应表头配置
const userHeadCells: HeadCell<UserData>[] = [
  { id: 'id', label: '用户ID', numeric: true },
  { id: 'name', label: '用户名', disablePadding: true },
  { id: 'email', label: '联系邮箱' },
  { id: 'createTime', label: '注册时间' },
];

// 页面中直接使用组件
<EnhancedTableHead<UserData>
  classes={classes}
  numSelected={selectedIds.length}
  order={order}
  orderBy={orderBy}
  onSelectAllClick={handleSelectAll}
  onRequestSort={handleSort}
  rowCount={userList.length}
  headCells={userHeadCells}
/>

补充说明

  • TypeScript会自动做类型校验,如果表头配置的id、排序字段不在当前数据结构的字段范围内,会直接抛出类型错误,保证类型安全
  • 大部分场景下不需要手动传入泛型参数,TS会根据传入的headCells自动推断对应的数据结构类型
  • 后续如果要扩展表格能力,也只需要在泛型类型中增加对应属性即可,所有使用的业务页面都可以无缝适配

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 20:27:02