TypeScript keyof 索引类型太宽

TypeScript keyof index type is too wide

抱歉,我确定这个问题已经在某个地方得到了回答,但我不确定 google。如果标题中的术语有误,请编辑我的问题。

我有这样的东西:

type RowData = Record<string, unknown> & {id: string}; 


type Column<T extends RowData, K extends keyof T> = {  
  key: K; 
  action: (value: T[K], rowData: T) => void;
}

type RowsAndColumns<T extends RowData> = {
  rows: Array<T>; 
  columns: Array<Column<T, keyof T>>; 
}

并且 TypeScript 应该能够推断出 action 函数的类型,通过检查行的形状,以及为列赋予的 key 值:

即:

function myFn<T extends RowData>(config: RowsAndColumns<T>) {

}

myFn({
  rows: [
    {id: "foo", 
      bar: "bar", 
      bing: "bing", 
      bong: {
        a: 99, 
        b: "aaa"
      }
    }
  ], 
  columns: [
    {
      key: "bar", 
      action: (value, rowData) => {
          console.log(value);
      }
    },
     {
      key: "bong", 
      action: (value, rowData) => {
          console.log(value.a); //Property 'a' does not exist on type 'string | { a: number; b: string; }'.

      }
    }
  ]
}); 

Playground Link

问题是,TypeScript 似乎将值的类型 (value: T[K]) 推导为 'the types of all accessible by all keys of T' 而不是仅使用 object.[=17 列中提供的键=]

为什么 TypeScript 会这样,我该如何解决?

好的答案是定义一些特定的术语和概念。

我想我想把我的 K extends keyof T 改成 'K is a keyof T, but only one key, and it never changes'。

如果您希望 T 的键像 "bong" | "bing" | ... 一样是 union of literals(而不仅仅是 string),那么您可以表达一个本身就是类型的类型keyof T.

中每个键 KColumn<T, K>union

我通常会立即 indexing (lookup) into a mapped type:

type SomeColumn<T extends RowData> = {
  [K in keyof T]-?: Column<T, K>
}[keyof T]

但您也可以通过 distributive conditional types:

type SomeColumn<T extends RowData> = keyof T extends infer K ?
  K extends keyof T ? Column<T, K> : never : never;

无论哪种方式,您的 RowsAndColumns 类型将使用 SomeColumn 而不是 Column:

type RowsAndColumns<T extends RowData> = {
  rows: Array<T>;
  columns: Array<SomeColumn<T>>;
}

这使您所需的用例按预期工作而没有编译错误:

myFn({
  rows: [
    {
      id: "foo",
      bar: "bar",
      bing: "bing",
      bong: {
        a: 99,
        b: "aaa"
      }
    }
  ],
  columns: [
    {
      key: "bar",
      action: (value, rowData) => {
        console.log(value);
      }
    },
    {
      key: "bong",
      action: (value, rowData) => {
        console.log(value.a);
      }
    },
  ]
});

Playground link to code