typescript 如何使接口中的属性类型相互依赖?

yvt65v4c  于 2022-12-24  发布在  TypeScript
关注(0)|答案(1)|浏览(186)

我似乎找不到一种动态的方法来使dataItem总是与field属性的值具有相同的类型。在下面的示例中,dataItem总是string | Date | number,而不是取决于field属性的特定类型。

interface TableColumn<D> {
  field: keyof D
  renderCell: (dataItem: D[keyof D]) => ReactNode
}

interface Data {
  foo: string
  bar: Date
  fiz: number
}

const data: Data = {
  foo: "my foo",
  bar: new Date(),
  fiz: 32
}

const tableColumn: TableColumn<Data> = {
  field: "foo",
  renderCell: (dataItem) => dataItem // here dataItem should be string only
}

const tableColumn1: TableColumn<Data> = {
  field: "bar",
  renderCell: (dataItem) => dataItem // here dataItem should be Date only
}

const tableColumn2: TableColumn<Data> = {
  field: "fiz",
  renderCell: (dataItem) => dataItem // here dataItem should be number only
}
o8x7eapl

o8x7eapl1#

一个接口不会这样工作;你可以把它设置得更像generic,比如TableColumn<D, K>,其中Kkeyof D中的一个特殊的键类型,但是你必须在所有地方都指定K
相反,我将使TableColumn<D>成为microsoft/TypeScript#47109中创造的 * 分布式对象类型 *,它求值为与keyof T中的每个K相对应的各个类型的并集。分布式对象类型是mapped type,它将keyof D中的每个属性值K转换为保存与该属性键相对应的所需类型的值。然后直接得到indexed intokeyof D,就像这样:

type TableColumn<D> = { [K in keyof D]-?:
  {
    field: K,
    renderCell: (dataItem: D[K]) => ReactNode
  }
}[keyof D]

在这里,我们生成Map类型{ [K in keyof D]-?: { field: K, renderCell: (dataItem: D[K]) => ReactNode }},对于Data,它变为如下形式

{
  foo: { field: "foo"; renderCell: (dataItem: string) => ReactNode }
  bar: { field: "bar"; renderCell: (dataItem: Date) => ReactNode }
  fiz: { field: "fiz"; renderCell: (dataItem: number) => ReactNode }
}

注意,Map修饰符-?只是确保D中的任何可选属性在Map类型中成为必需的,因为我们不希望任何undefined在这里浮动。
然后,我们通过{...}[keyof D]索引到该类型,它将Map的类型转换为其值类型的并集,最终生成:

type TCD = TableColumn<Data>
/* type TCD = {
    field: "foo";
    renderCell: (dataItem: string) => ReactNode;
} | {
    field: "bar";
    renderCell: (dataItem: Date) => ReactNode;
} | {
    field: "fiz";
    renderCell: (dataItem: number) => ReactNode;
} */

这就是您想要的联合类型;实际上,它是一个判别并集,其中field是判别属性,它允许编译器从field的类型推断出renderCell的类型,从而在renderCell的未注解回调参数上给出所需的上下文类型:

const tableColumn: TableColumn<Data> = {
  field: "foo",
  renderCell: (dataItem) => dataItem
  // (parameter) dataItem: string
}

const tableColumn1: TableColumn<Data> = {
  field: "bar",
  renderCell: (dataItem) => dataItem
  // (parameter) dataItem: Date
}

const tableColumn2: TableColumn<Data> = {
  field: "fiz",
  renderCell: (dataItem) => dataItem
  // (parameter) dataItem: number
}

Playground代码链接

相关问题