¿Cuál es el tipo de registro en mecanografiado?


183

¿Qué Record<K, T>significa en mecanografiado?

Typecript 2.1 introdujo el Recordtipo, describiéndolo en un ejemplo:

// For every properties K of type T, transform it to U
function mapObject<K extends string, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U>

ver Typecript 2.1

Y el Tipos avanzada página menciona Recordbajo los tipos mapeados en dirección al lado Readonly, Partialy Pick, en lo que parece ser su definición:

type Record<K extends string, T> = {
    [P in K]: T;
}

Readonly, Partial y Pick son homomórficos, mientras que Record no lo es. Una pista de que Record no es homomórfico es que no se necesita un tipo de entrada para copiar propiedades de:

type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>

Y eso es. Además de las citas anteriores, no hay otra mención Recorden typescriptlang.org .

Preguntas

  1. ¿Alguien puede dar una definición simple de lo que Recordes?

  2. ¿Es Record<K,T>simplemente una forma de decir "todas las propiedades en este objeto tendrán tipo T"? Probablemente no todas las propiedades, ya que Ktiene algún propósito ...

  3. ¿El Kgenérico prohíbe claves adicionales en el objeto que no lo son K, o las permite y solo indica que sus propiedades no se transforman T?

  4. Con el ejemplo dado:

     type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>

¿Es exactamente lo mismo que esto ?:

    type ThreeStringProps = {prop1: string, prop2: string, prop3: string}

66
La respuesta a 4. es prácticamente "sí", por lo que probablemente debería responder a sus otras preguntas.
jcalz

Respuestas:


212
  1. ¿Alguien puede dar una definición simple de lo que Recordes?

A Record<K, T>es un tipo de objeto cuyas claves de propiedad son Ky cuyos valores de propiedad son T. Es decir, keyof Record<K, T>es equivalente a K, y Record<K, T>[K]es (básicamente) equivalente a T.

  1. ¿Es Record<K,T>simplemente una forma de decir "todas las propiedades en este objeto tendrán tipo T"? Probablemente no todos los objetos, ya que Ktiene algún propósito ...

Como observa, Ktiene un propósito ... limitar las claves de propiedad a valores particulares. Si desea aceptar todas las claves posibles con valores de cadena, puede hacer algo como Record<string, T>, pero la forma idiomática de hacerlo es utilizar una firma de índice como { [k: string]: T }.

  1. ¿El Kgenérico prohíbe claves adicionales en el objeto que no lo son K, o las permite y solo indica que sus propiedades no se transforman T?

No "prohíbe" exactamente claves adicionales: después de todo, generalmente se permite que un valor tenga propiedades no mencionadas explícitamente en su tipo ... pero no reconocería que tales propiedades existen:

declare const x: Record<"a", string>;
x.b; // error, Property 'b' does not exist on type 'Record<"a", string>'

y los trataría como propiedades en exceso que a veces se rechazan:

declare function acceptR(x: Record<"a", string>): void;
acceptR({a: "hey", b: "you"}); // error, Object literal may only specify known properties

y a veces aceptado:

const y = {a: "hey", b: "you"};
acceptR(y); // okay
  1. Con el ejemplo dado:

    type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>

    ¿Es exactamente lo mismo que esto ?:

    type ThreeStringProps = {prop1: string, prop2: string, prop3: string}

¡Si!

Espero que ayude. ¡Buena suerte!


1
Muy aprendido y una pregunta ¿por qué "la forma idiomática de hacerlo es usar una firma de índice" no una de registro? No encuentro ninguna información relacionada sobre esta "forma idiomática".
legend80s

2
Puedes usar Record<string, V>para decir {[x: string]: V}si quieres; Probablemente incluso he hecho esto yo mismo. La versión de firma de índice es más directa: son del mismo tipo, pero la primera es un alias de tipo de un tipo mapeado que se evalúa como una firma de índice, mientras que la segunda es solo la firma de índice directamente. En igualdad de condiciones, recomendaría lo último. Del mismo modo, no lo usaría Record<"a", string>en lugar de a {a: string}menos que hubiera alguna otra razón contextual convincente para hacerlo.
jcalz

1
" Todo lo demás es igual, recomendaría lo último " . ¿Por qué es eso? Mi auto pre-Typecript está de acuerdo, pero sé que el primero será más, um, auto-comentarios para personas que vienen del lado C #, por ejemplo, y no peor para JavaScript-to-Typecripters. ¿Está interesado en omitir el paso de transpilación para esas construcciones?
ruffin

1
Solo mi opinión: el comportamiento de Record<string, V>solo tiene sentido si ya sabes cómo funcionan las firmas de índice en TypeScript. Por ejemplo, dado x: Record<string, string>, x.fooaparentemente será un stringmomento de compilación, pero en realidad es probable que lo sea string | undefined. Esta es una brecha en cómo --strictNullChecksfunciona (ver # 13778 ). Yo prefiero que se ocupan de los recién llegados {[x: string]: V}directamente en lugar de esperar que siga la cadena de Record<string, V>a través {[P in string]: V}de la conducta índice de la firma.
jcalz

Quería señalar que, lógicamente, un tipo se puede definir como un conjunto de todos los valores contenidos dentro del tipo. Dada esta interpretación, creo que Record <string, V> es razonable como una abstracción para simplificar el código en lugar de presentar todos los valores posibles. Es similar al ejemplo en la documentación de tipos de utilidad: Registro <T, V> donde tipo T = 'a' | 'b' | 'C'. Nunca hacer Record <'a', string> no es un buen ejemplo de contador, porque no sigue el mismo patrón. Tampoco agrega para reutilizar o simplificar el código a través de la abstracción como lo hacen los otros ejemplos.
Scott Leonard

69

Un registro le permite crear un nuevo tipo a partir de una unión. Los valores en la Unión se utilizan como atributos del nuevo tipo.

Por ejemplo, digamos que tengo una Unión como esta:

type CatNames = "miffy" | "boris" | "mordred";

Ahora quiero crear un objeto que contenga información sobre todos los gatos, puedo crear un nuevo tipo usando los valores de CatName Union como claves.

type CatList = Record<CatNames, {age: number}>

Si quiero satisfacer esta CatList, debo crear un objeto como este:

const cats:CatList = {
  miffy: { age:99 },
  boris: { age:16 },
  mordred: { age:600 }
}

Obtienes seguridad de tipo muy fuerte:

  • Si olvido un gato, me sale un error.
  • Si agrego un gato que no está permitido, aparece un error.
  • Si luego cambio CatNames, aparece un error. Esto es especialmente útil porque es probable que CatNames se importe desde otro archivo y se use en muchos lugares.

Ejemplo de reacción en el mundo real.

Utilicé esto recientemente para crear un componente de estado. El componente recibiría un accesorio de estado y luego representaría un icono. He simplificado mucho el código aquí con fines ilustrativos

Tuve una unión como esta:

type Statuses = "failed" | "complete";

Usé esto para crear un objeto como este:

const icons: Record<
  Statuses,
  { iconType: IconTypes; iconColor: IconColors }
> = {
  failed: {
    iconType: "warning",
    iconColor: "red"
  },
  complete: {
    iconType: "check",
    iconColor: "green"
  };

Entonces podría renderizar mediante la desestructuración de un elemento del objeto en accesorios, así:

const Status = ({status}) => <Icon {...icons[status]} />

Si la unión de estados se extiende o cambia más tarde, sé que mi componente de estado no se compilará y recibiré un error que puedo solucionar de inmediato. Esto me permite agregar estados de error adicionales a la aplicación.

Tenga en cuenta que la aplicación real tenía docenas de estados de error a los que se hizo referencia en varios lugares, por lo que este tipo de seguridad fue extremadamente útil.


¿Supongo que la mayoría de las veces type Statusesvive en tipings NO definidos por usted? De lo contrario, puedo ver algo así como una interfaz con una enumeración que se ajusta mejor, ¿verdad?
Victorio Berra

Hola @victorio, no estoy seguro de cómo una enumeración resolvería el problema, no obtienes un error en una enumeración si pierdes una clave. Es solo un mapeo entre claves y valores.
superluminary

1
Ahora entiendo lo que quieres decir. Viniendo de C # no tenemos formas inteligentes de hacerlo. Lo más cercano sería un diccionario de Dictionary<enum, additional_metadata>. El tipo de registro es una excelente manera de representar ese patrón de enumeración + metadatos.
Victorio Berra
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.