Búsqueda de sitios web

El tipo keyof del índice de TypeScript es demasiado amplio


Disculpas, estoy seguro de que esto ya ha sido respondido en algún lugar, pero no sé qué buscar en Google. Por favor, edita mi pregunta si los términos en el título son incorrectos.

Tengo algo así:

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

Y TypeScript debería poder inferir los tipos de las funciones action, examinando la forma de las filas y qué valor de key se ha asignado a la columna:

es decir:

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

      }
    }
  ]
}); 

Enlace del parque

El problema es que TypeScript parece estar derivando el tipo de value (value: T[K]) como 'los tipos de todos los accesibles por todas las claves de T' en lugar de usar solo la clave proporcionada en el objeto de la columna.

¿Por qué TypeScript está haciendo esto y cómo puedo solucionarlo?

Lo que haría una buena respuesta es definir algunos términos y conceptos específicos.

Me imagino que quiero cambiar mi K extends keyof T a algo como 'K es una clave de T, pero solo una clave, y nunca cambia'.

Respuesta #1

Si esperas que las claves de T sean una unión de literales como "bong" | "bing" | ... (y no solo string), entonces puedes expresar un tipo que sea en sí mismo la unión de Column<T, K> para cada clave K en keyof T.

Normalmente hago esto mediante la indexación inmediata (búsqueda) en un tipo mapeado:

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

pero también puedes hacerlo a través de tipos condicionales distributivos:

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

De cualquier manera, su tipo RowsAndColumns usaría entonces SomeColumn en lugar de Column:

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

Y esto hace que su caso de uso deseado funcione como se espera sin errores de compilación:

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

Enlace de Playground al código

Artículos relacionados