typescript - ¿Qué significa [keyof T] después de la llave de cierre en la definición de tipo?
Mirando la documentación de Typescript en esta URL https://www.typescriptlang.org/docs/handbook/advanced-types.html
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
¿Alguien puede explicarme qué significa esto [keyof T] después de la llave de cierre? ¿Hay alguna documentación sobre esta sintaxis?
Respuesta #1
Eso se llama un "tipo de búsqueda".
keyof Xdevuelve todas las claves de un tiposi
interface a {
foo: never
bar: string
baz: number
}
entonces type not_here=a['foo'] será never
pero los tipos de búsqueda también soportan pasar keyof Something, así que
a[keyof a] sería la unión de todos los tipos de las claves/propiedades de a, que es never | string | number. Aunque never no tiene significado allí, por lo que TS lo ignora automáticamente, resultando en string | number.
(por supuesto, puedes hacer a[keyof b], no hay ninguna restricción aquí)
Encuentro que la mejor manera de entender tipos complejos como este es descomponerlos en pasos como lo hice aquí:
interface test {
a: string
b: () => void
c: () => void
}
type T_A<T> = {
[K in keyof T]: T[K]
};
type T_B<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
};
type T_C<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type a = T_A<test>
type b = T_B<test>
type c = T_C<test>
Con esto puedes ver los pasos individuales que se tomaron para obtener el resultado deseado, que es "la unión de claves que son de tipo Función".
Respuesta #2
keyof T significa claves válidas para el tipo T (probablemente ya lo sabías). Cuando tienes [x] después de una interfaz o tipo unión, selecciona el tipo de la propiedad/miembro con el nombre x. Por ejemplo (enlace al playground):
interface Example {
a: number;
b: string;
}
type E1 = Example["a"];
/*
type E1 = number;
*/
Hasta donde sé, estos "tipos de búsqueda" solo están documentados en las notas de la versión 2.1 de TypeScript aquí:
keyofy Tipos de Búsqueda En JavaScript es bastante común tener APIs que esperan nombres de propiedades como parámetros, pero hasta ahora no ha sido posible expresar las relaciones de tipo que ocurren en esas APIs.Ingrese Consulta de Tipo Indexado o
keyof; Una consulta de tipo indexadokeyof Tproduce el tipo de nombres de propiedad permitidos para T. Un tipokeyof Tse considera un subtipo de string.Ejemplo
interface Person { name: string; age: number; location: string; } type K1 = keyof Person; // "name" | "age" | "location" type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ... type K3 = keyof { [x: string]: Person }; // stringEl dual de esto son los tipos de acceso indexado, también llamados tipos de búsqueda. Sintácticamente, se parecen exactamente a un acceso a un elemento, pero se escriben como tipos:
Ejemplo
type P1 = Person["name"]; // string type P2 = Person["name" | "age"]; // string | number type P3 = string["charAt"]; // (pos: number) => string type P4 = string[]["push"]; // (...items: string[]) => number type P5 = string[][0]; // stringPuedes usar este patrón con otras partes del sistema de tipos para obtener búsquedas seguras en cuanto al tipo.
function getProperty<T, K extends keyof T>(obj: T, key: K) { return obj[key]; // Inferred type is T[K] } function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]) { obj[key] = value; } let x = { foo: 10, bar: "hello!" }; let foo = getProperty(x, "foo"); // number let bar = getProperty(x, "bar"); // string let oops = getProperty(x, "wargarbl"); // Error! "wargarbl" is not "foo" | "bar" setProperty(x, "foo", "string"); // Error!, string expected number
La parte principal de FunctionPropertyNames<T> produce una interfaz con miembros de tipo never para todas las propiedades de T que no son de tipo función, y el tipo de miembro original para aquellas que sí lo son. Por ejemplo, como se muestra en el ejemplo, si haces esto:
interface Part {
id: number;
name: string;
subparts: Part[];
updatePart(newName: string): void;
}
type T1 = FunctionPropertyNames<Part>;
T1 termina siendo "updatePart" porque esa es la única clave de T (Part) que tiene un tipo función. Sin la parte [keyof T], obtendrías la interfaz con los miembros never en su lugar (enlace al playground):
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type Example<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}/* Same as above, but without [keyof T] here*/;
interface Part {
id: number;
name: string;
subparts: Part[];
updatePart(newName: string): void;
}
type T1 = FunctionPropertyNames<Part>;
/*
type T1 = "updatePart"
*/
type E1 = Example<Part>;
/*
type E1 = {
id: never;
name: never;
subparts: never;
updatePart: "updatePart";
}
*/
Es la parte [keyof T] la que hace que FunctionPropertyNames proporcione los nombres de las propiedades de tipo función, en lugar de las propias propiedades de tipo función (y las de tipo never).
