Typ indeksu klucza TypeScript jest za szeroki

Nov 08 2020

Przepraszamy, jestem pewien, że gdzieś na to odpowiedziano, ale nie jestem pewien, co mam znaleźć w Google. Proszę edytować moje pytanie, jeśli terminy w tytule są nieprawidłowe.

Mam coś takiego:

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>>; 
}

A TypeScript powinien być w stanie wywnioskować typy actionfunkcji, badając kształt wierszy i jaką keywartość nadano kolumnie:

to znaczy:

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; }'.

      }
    }
  ]
}); 

Link do placu zabaw

Problem polega na tym, że TypeScript wydaje się wyprowadzać typ value ( value: T[K]) jako „typy wszystkich dostępnych dla wszystkich kluczy T”, zamiast używać tylko klucza dostarczonego w obiekcie kolumny.

Dlaczego TypeScript to robi i jak mogę to rozwiązać?

Dobrą odpowiedzią byłoby zdefiniowanie określonych terminów i pojęć.

Wyobrażam sobie, że chcę zmienić moje, K extends keyof Taby było coś w rodzaju „K jest kluczem T, ale tylko jednym klawiszem i nigdy się nie zmienia”.

Odpowiedzi

5 jcalz Nov 09 2020 at 14:21

Jeśli oczekujesz klucze Tbyć zjednoczenie z literały takich jak "bong" | "bing" | ...(i nie tylko string), to można wyrazić typ, który sam jest zjednoczenie z Column<T, K>dla każdego klucza Kw keyof T.

Zwykle robię to poprzez natychmiastowe indeksowanie (wyszukiwanie) do mapowanego typu :

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

ale możesz to również zrobić za pomocą dystrybucyjnych typów warunkowych :

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

Tak czy inaczej, twój RowsAndColumnstyp używałby wtedy SomeColumnzamiast Column:

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

A to sprawia, że ​​pożądany przypadek użycia działa zgodnie z oczekiwaniami bez błędów kompilacji:

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);
      }
    },
  ]
});

Link do kodu placu zabaw