typescript - 从数组类型泛型参数推断记录类型

标签 typescript typescript-generics

我正在尝试为通用表格组件编写一种类型,该组件接受列列表(每列都有名称和类型)以及需要符合列规定的形状的行列表。

我的代码仅在添加显式类型时才有效,但我更希望 typescript 能够为我推断这些类型:

enum ColumnType {
  text = "text",
  numeric = "numeric",
}

interface Props<T extends Record<string, ColumnType>> {
  columns: Array<{ name: keyof T; columnType: ColumnType }>;
  rows: Array<
    {
      [K in keyof T]: T[K] extends ColumnType.numeric
        ? { content: number }
        : { content: string };
    }
  >;
}

function consume<T extends Record<string, ColumnType>>(arg: Props<T>): void {}

// missing fields, should not typecheck
consume({
  columns: [
    { name: "a", columnType: ColumnType.numeric },
    { name: "b", columnType: ColumnType.text },
  ],
  rows: [{}], 
});

// type mismatch, should not typecheck
consume({
  columns: [
    { name: "a", columnType: ColumnType.numeric },
    { name: "b", columnType: ColumnType.text },
  ],
  rows: [{ a: { content: "asdf" }, b: { content: 42 } }], 
});

// should typecheck, but doesn't
consume({
  columns: [
    { name: "a", columnType: ColumnType.numeric },
    { name: "b", columnType: ColumnType.text },
  ],
  rows: [{ a: { content: 42 }, b: { content: "asdf" } }],
});

// works with explicit type argument
consume<{ a: ColumnType.numeric; b: ColumnType.text }>({
  columns: [
    { name: "a", columnType: ColumnType.numeric },
    { name: "b", columnType: ColumnType.text },
  ],
  rows: [{ a: { content: 42 }, b: { content: "asdf" } }],
});

有没有办法用 typescript 来完成这个任务?

Typescript playground

最佳答案

第一步是捕获有关列的更多信息。我们可以通过将整个数组设置为类型参数来做到这一点。为了确保我们捕获文字类型,我们将为 props 的名称和列类型添加一些类型参数,以帮助 TS 推断这些字段的文字类型,而不是推断其更广泛的类型。

function consume<T extends Array<{ name: K; columnType: CT }>, K extends PropertyKey, CT extends ColumnType>(arg: Props<T>): void {}

我们还希望捕获元组类型而不是数组类型,以便更轻松地处理数组的每个元素。为此,我们可以将 [] | 添加到类型约束中:

function consume<T extends [] | Array<{ name: K; columnType: CT }>, K extends PropertyKey, CT extends ColumnType>(arg: Props<T>): void {}

为了将数组的元素映射到对象中的字段,需要使用映射类型。

这里我们有两个选择。

第一个选项是在映射类型中使用 as 子句将元组的每个元素转换为字段。我们只需要过滤元组的索引(我们不需要数组成员)。为此,我们可以与 `${number}` 相交(元组索引实际上在类型系统 "0", "1"... 中表示为字符串)

type Row<T extends Array<{ name: PropertyKey; columnType: ColumnType }>> =  {} & {
  [K in keyof T & `${number}` as T[K]['name']]: T[K]['columnType'] extends ColumnType.numeric
      ? { content: number }
      : { content: string };
}

与旧版本 TS 兼容的另一个选项是使用 number 进行索引,以获取元组所有元素的并集,映射由 name 产生的并集code> 属性,然后要获取每个字段的类型,请使用 Exatct 提取相应的联合组成部分:

type Row<T extends Record<number, { name: PropertyKey; columnType: ColumnType }>> =  {} & {
    [K in T[number]['name']]: Extract<T[number], { name: K } >['columnType'] extends ColumnType.numeric
      ? { content: number }
      : { content: string };
  }

把它们放在一起我们得到:

enum ColumnType {
  text = "text",
  numeric = "numeric",
}

type Row<T extends Array<{ name: PropertyKey; columnType: ColumnType }>> =  {} & {
  [K in keyof T & `${number}` as T[K]['name']]: T[K]['columnType'] extends ColumnType.numeric
      ? { content: number }
      : { content: string };
}
type Props<T extends Array<{ name: PropertyKey; columnType: ColumnType }>> = {
  columns: T;
  rows: Array<Row<T>>;
}

function consume<T extends [] | Array<{ name: K; columnType: CT }>, K extends PropertyKey, CT extends ColumnType>(arg: Props<T>): void {}

Playground Link

关于typescript - 从数组类型泛型参数推断记录类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74099144/

相关文章:

javascript - typescript 错误 TS1128 : Declaration or statement expected

typescript - 如何删除重复的类型并避免以 never 结尾?

arrays - typescript :如何将键数组转换为联合类型的对象?

Angular 4 - 从不起作用的子组件调用父方法

angular - 验证 URL

typescript - T[keyof T] 是否像分配条件类型一样工作?

arrays - 制作通用接口(interface)数组

typescript - 将 InstanceType<T> 与工厂函数一起使用

typescript - 类型不可分配给类型 2322

javascript - 在 React 中使用泛型键入 defaultProp 箭头函数方法