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

如何为React TypeScript组件数组中每个对象单独推断data类型?

问题:为React TypeScript组件的数组元素单独推断data属性类型

我有一个接收数组作为输入的React TypeScript组件,该组件通过遍历数组将每个对象渲染为Card组件。数组中的每个对象包含多个属性,其中data属性的类型在不同对象中可能存在差异。

当向该组件传入包含不同data类型对象的数组时,TypeScript编译器会根据数组中的第一个对象推断data的类型,这会导致渲染后续具有不同data类型的对象时出现类型错误。

以下是Card组件及ListPrintingPoints组件的示例代码:

import {
  forwardRef,
  ReactNode,
  HTMLAttributes,
  Ref,
  ForwardedRef,
  ElementType,
} from 'react';

export type Tabulated<T> = {
  header?: string;
  data: T;
  dataContainer?: ElementType;
  formatter?: (data: T) => ReactNode;
  isVisible?: boolean;
};

export interface CardProps<T> extends HTMLAttributes<HTMLElement> {
  readonly tabulated: Tabulated<T>[];
  readonly className?: string;
}

const CardContainer = <T,>(
  { tabulated, className = '', ...rest }: CardProps<T>,
  ref: Ref<HTMLElement>
) => {
  return (
    <article
      className={`flex flex-col sm:flex-row bg-white rounded-md p-4 gap-2 ${className}`}
      ref={ref}
      {...rest}
    >
      {tabulated.map(
        ({
          header,
          data,
          dataContainer: Container = 'p',
          formatter,
          isVisible = true,
        }) => (
          <>
            {isVisible && (
              <div className={`${header ? 'flex-1' : 'w-fit'}`}>
                {header && (
                  <h3 className="text-sm sm:text-base leading-6 font-normal text-gray-500 mb-1">
                    {header}
                  </h3>
                )}
                <Container
                  className={`${
                    Container === 'p'
                      ? 'text-sm sm:text-base leading-normal font-bold text-gray-700 w-full'
                      : 'w-full'
                  } ${!header ? 'h-full' : ''} `}
                >
                  {!data && !formatter && '-'}
                  {data && !formatter && (data as ReactNode)}
                  {data && formatter && formatter(data)}
                </Container>
              </div>
            )}
          </>
        )
      )}
    </article>
  );
};

export const Card = forwardRef(CardContainer) as <T>(
  props: CardProps<T> & {
    ref?: ForwardedRef<HTMLElement>;
  }
) => ReturnType<typeof CardContainer>;

type PrintingPoint = {
   serialCode: string;
   macs: {
      ethMac: string;
      wlanMac: string;
   }
}

const printingPoints: PrintingPoint[] = [
    {
      serialCode: 'oDYxPieXt98mPuDVp2GF',
      macs: {
       ethMac: 'B7:68:83:5E:B7:27',
       wlanMac: '6A:AF:90:44:92:9E'
      }
    },
    {
      serialCode: 'MjGxwY9uEgHvtq78HHsH',
      macs: {
       ethMac: '9A:66:00:F2:B9:03',
       wlanMac: '0D:C3:75:1B:4C:A6'
      }
    }
]

interface ListPrintingPointsProps {
    printingPoints: PrintingPoint[]
}

const ListPrintingPoints = ({ printingPoints }: ListPrintingPointsProps) => {
   return ( 
       <ul>
           {printingPoints.map((printingPoint) => (
            <li key={printingPoint.serialCode}>
              <Card
                tabulated={[
                  {
                    header: 'Serial Code',
                    data: printingPoint.serialCode,
                  },
                  {
                    header: 'Ethernet:MAC',
                    data: printingPoint.macs.ethMac,
                    formatter: (data) => data.toUpperCase(),
                  },
                  {
                    header: 'WLAN:MAC',
                    data: printingPoint.macs.wlanMac,
                    formatter: (data) => data.toUpperCase(),
                  },
                  {
                    dataContainer: 'div',
                    data: ( 
                      <div className="flex justify-end items-center w-full h-full">
                        <button>
                          <ArrowRightIcon className="stroke-2 text-brand-500 w-5 h-5" />
                        </button>
                      </div>
                    ),
                  },
                ]}
              />
            </li>
          ))}
        </ul>
    )
}

请问是否可以为数组中的每个对象单独推断data属性的类型,以避免上述类型错误问题?


解决方案

问题根源在于原组件用单一泛型T约束整个tabulated数组的data类型,导致所有元素的data必须是同一类型,无法支持不同类型的data。我们可以通过修改类型定义,让数组中每个元素的data类型独立,同时保证formatter与对应data的类型匹配。

具体修改步骤

1. 调整类型定义,支持独立的元素类型

将CardProps的单一泛型约束改为接受包含任意Tabulated元素的数组,每个元素可以拥有自己的data类型:

export type Tabulated<T> = {
  header?: string;
  data: T;
  dataContainer?: ElementType;
  formatter?: (data: T) => ReactNode;
  isVisible?: boolean;
};

// 移除单一泛型T,改为接受任意Tabulated元素的数组
export interface CardProps extends HTMLAttributes<HTMLElement> {
  readonly tabulated: readonly Tabulated<unknown>[];
  readonly className?: string;
}

2. 更新组件和forwardRef的类型声明

去掉组件的单一泛型T,改为支持任意Tabulated元素的数组:

const CardContainer = (
  { tabulated, className = '', ...rest }: CardProps,
  ref: Ref<HTMLElement>
) => {
  // 原有组件逻辑保持不变
  return (
    <article
      className={`flex flex-col sm:flex-row bg-white rounded-md p-4 gap-2 ${className}`}
      ref={ref}
      {...rest}
    >
      {tabulated.map(
        ({
          header,
          data,
          dataContainer: Container = 'p',
          formatter,
          isVisible = true,
        }) => (
          <>
            {isVisible && (
              <div className={`${header ? 'flex-1' : 'w-fit'}`}>
                {header && (
                  <h3 className="text-sm sm:text-base leading-6 font-normal text-gray-500 mb-1">
                    {header}
                  </h3>
                )}
                <Container
                  className={`${
                    Container === 'p'
                      ? 'text-sm sm:text-base leading-normal font-bold text-gray-700 w-full'
                      : 'w-full'
                  } ${!header ? 'h-full' : ''} `}
                >
                  {!data && !formatter && '-'}
                  {data && !formatter && (data as ReactNode)}
                  {data && formatter && formatter(data)}
                </Container>
              </div>
            )}
          </>
        )
      )}
    </article>
  );
};

export const Card = forwardRef(CardContainer) as (
  props: CardProps & { ref?: ForwardedRef<HTMLElement> }
) => ReturnType<typeof CardContainer>;

3. 更严谨的类型约束(可选)

如果需要保留每个元素的类型信息,让TypeScript能准确检查formatter的参数类型,可以使用泛型数组:

// 用泛型数组定义CardProps,保留每个元素的类型信息
export type CardProps<T extends readonly Tabulated<any>[]> = HTMLAttributes<HTMLElement> & {
  readonly tabulated: T;
  readonly className?: string;
};

const CardContainer = <T extends readonly Tabulated<any>[]>(
  { tabulated, className = '', ...rest }: CardProps<T>,
  ref: Ref<HTMLElement>
) => {
  // 原有组件逻辑不变
};

export const Card = forwardRef(CardContainer) as <T extends readonly Tabulated<any>[]>(
  props: CardProps<T> & { ref?: ForwardedRef<HTMLElement> }
) => ReturnType<typeof CardContainer>;

效果验证

修改后,传入包含不同data类型的tabulated数组时,TypeScript会为每个元素单独推断data的类型:

  • 前三个元素的data为string类型,formatter的参数会被正确推断为string,支持toUpperCase()调用;
  • 最后一个元素的data为ReactNode类型,也能正常通过类型检查。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 05:09:52