Búsqueda de sitios web

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".

  1. keyof X devuelve todas las claves de un tipo

  2. si

    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í:

keyof y 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 indexado keyof T produce el tipo de nombres de propiedad permitidos para T. Un tipo keyof T se 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 }; // string

El 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]; // string

Puedes 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).

Artículos relacionados