typescript 列数组中rowData和cellData的类型推断

ccrfmcuu  于 2023-01-03  发布在  TypeScript
关注(0)|答案(1)|浏览(122)

我正在尝试为以下数据结构创建一个类型:

const columns = [
  {
    dataKey: 'field1',
    label: 'Field 1',
    render: ({ rowData, cellData }) => {
      return null;
    },
  },
  {
    label: 'Actions',
    render: ({ rowData }) => {
      return null;
    },
  },
]

我不想用泛型类型来定义列,也不想在列表的每个对象中推断rowData和cellData的类型,如果dataKey不在对象中,cellData应该是未定义的。
当我指定dataKey时,rowData和cellData都显示为正确的类型,但如果没有它,rowData将变为any,但如果我输入任何不属于正确类型的值,typescript将报告错误,但不会推断正确的类型。

type ColumnDef<T, U> = {
  label: string;
  render: (args: { rowData: T; cellData: U }) => null;
};

type ColumnWithoutKey<T> = ColumnDef<T, undefined>;

type ColumnWithKey<T> = {
  [K in keyof T]: T[K] extends infer TK
    ? { dataKey: K } & ColumnDef<T, TK>
    : never;
}[keyof T];

type HasDataKey = {
  dataKey: string;
};

type Column<T, U = any> = U extends HasDataKey
  ? ColumnWithKey<T>
  : ColumnWithoutKey<T>;

type Columns<T> = Array<Column<T>>;

type Data = {
  field1: string;
  field2: number; // only work if I have two or more fields
};

const columns: Columns<Data> = [
  {
    dataKey: 'field1', // can autocomplete the dataKey values
    label: 'Field 1',
    // rowData type is Data and cellData is string, both are correct
    render: ({ rowData, cellData }) => {
      return null;
    },
  },
  {
    label: 'Actions',
    // both rowData and cellData types are inferred as any
    render: ({ rowData, cellData }) => {
      return null;
    },
  },
  {
    label: 'Actions',
    // will complain that field3 doesn't exist in the render type, but if I change it to field2, the type continues any
    render: ({ rowData: { field3 } }) => {
      return null;
    },
  },
];

Playground

c9qzyr3d

c9qzyr3d1#

只需在ColumnWithoutKey类型中添加一个显式(可选)dataKey属性,类型为never,这样Columns类型就成为一个正确的可区分并集(具有dataKey的列+具有未定义dataKey的列):

type ColumnWithoutKey<T> = {
  dataKey?: never; // Explicit undefined discriminant property
} & ColumnDef<T, undefined>;

现在它按预期工作:

const columns: Columns<Data> = [
  {
    dataKey: 'field1', // can autocomplete the dataKey values
    label: 'Field 1',
    // rowData type is Data and cellData is string, both are correct
    render: ({
      rowData,
      //^? Data
      cellData
      //^? string
    }) => {
      return null;
    },
  },
  {
    label: 'Actions',
    render: ({ // Okay
      rowData,
      //^? Data
      cellData
      //^? undefined
    }) => {
      return null;
    },
  },
  {
    label: 'Actions 2',
    // will complain that field3 doesn't exist in the render type
    render: ({ rowData: { field3 } }) => { // Error: Property 'field3' does not exist on type 'Data'.
      return null;
    },
  },
  {
    label: 'Actions 3',
    render: ({ rowData: { field2 } }) => { // Okay
      //                    ^? number
      return null;
    },
  },
];

也适用于单个特性数据类型:

const columns2: Columns<{
  field0: boolean;
}> = [
    {
      dataKey: 'field0',
      label: 'Field Zero',
      render: ({ // Okay
        rowData,
        //^? { field0: boolean; }
        cellData
        //^? boolean
      }) => null
    },
    {
      label: 'Row actions',
      render: ({ rowData: { field3 } }) => null // Error: Property 'field3' does not exist on type '{ field0: boolean; }'.
    },
    {
      label: 'Row actions 2',
      render: ({ rowData }) => null // Okay
      //         ^? { field0: boolean; }
    }
  ];

Playground链接
顺便说一句,你可以简化你的ColumnWithKeyColumn类型,在这些情况下不需要条件类型:

type ColumnWithKey<T> = {
  [K in keyof T]: { // No need for conditional type and inference
    dataKey: K
  } & ColumnDef<T, T[K]>
}[keyof T];

type Column<T> = // No need for conditional type, direct union
  | ColumnWithKey<T>
  | ColumnWithoutKey<T>;

Playground链接

相关问题