Qual é o tipo de registro em texto datilografado?


183

O que Record<K, T>significa no texto datilografado?

O TypeScript 2.1 introduziu o Recordtipo, descrevendo-o em um exemplo:

// 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>

veja Texto datilografado 2.1

E o Tipos Avançada página menciona Recordsob os Tipos mapeadas em direção ao lado Readonly, Partiale Pick, no que parece ser a sua definição:

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

Somente leitura, Partial e Pick são homomórficos, enquanto Record não. Uma pista de que Record não é homomórfico é que não é necessário um tipo de entrada para copiar propriedades de:

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

E é isso. Além das citações acima, não há outra menção Recordno typescriptlang.org .

Questões

  1. Alguém pode dar uma definição simples do que Recordé?

  2. É Record<K,T>apenas uma maneira de dizer "todas as propriedades desse objeto terão tipo T"? Provavelmente nem todas as propriedades, pois Ktem algum objetivo ...

  3. Os Kgenéricos proíbem chaves adicionais no objeto que não são Kou permitem ou apenas indicam que suas propriedades não são transformadas T?

  4. Com o exemplo dado:

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

É exatamente o mesmo que isso ?:

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

6
A resposta para 4. é praticamente "sim", de modo que provavelmente deve responder suas outras perguntas.
jcalz

Respostas:


211
  1. Alguém pode dar uma definição simples do que Recordé?

A Record<K, T>é um tipo de objeto cujas chaves de propriedade são Ke cujos valores de propriedade são T. Ou seja, keyof Record<K, T>é equivalente a Ke Record<K, T>[K]é (basicamente) equivalente a T.

  1. É Record<K,T>apenas uma maneira de dizer "todas as propriedades desse objeto terão tipo T"? Provavelmente nem todos os objetos, pois Ktem algum objetivo ...

Como você observa, Ktem um objetivo ... limitar as chaves de propriedade a valores específicos. Se você deseja aceitar todas as chaves possíveis com valor de sequência, pode fazer algo como Record<string, T>, mas a maneira idiomática de fazer isso é usar uma assinatura de índice como { [k: string]: T }.

  1. Os Kgenéricos proíbem chaves adicionais no objeto que não são Kou permitem ou apenas indicam que suas propriedades não são transformadas T?

Ele não "proíbe" exatamente chaves adicionais: afinal, geralmente é permitido que um valor tenha propriedades não mencionadas explicitamente em seu tipo ... mas não reconheceria que essas propriedades existem:

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

e os trataria como propriedades em excesso que às vezes são rejeitadas:

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

e às vezes aceito:

const y = {a: "hey", b: "you"};
acceptR(y); // okay
  1. Com o exemplo dado:

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

    É exatamente o mesmo que isso ?:

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

Sim!

Espero que ajude. Boa sorte!


1
Muito aprendido e uma pergunta por que "a maneira idiomática de fazer isso é usar uma assinatura de índice" não uma assinatura de registro? Não encontro informações relacionadas a esse "modo idiomático".
legend80s

2
Você pode usar Record<string, V>para significar {[x: string]: V}se quiser; Eu provavelmente já fiz isso sozinho. A versão da assinatura de índice é mais direta: eles são do mesmo tipo, mas o primeiro é um alias de tipo de um tipo mapeado que é avaliado como uma assinatura de índice, enquanto o último é apenas a assinatura de índice diretamente. Tudo o resto é igual, eu recomendo o último. Da mesma forma, eu não usaria Record<"a", string>no lugar, a {a: string}menos que houvesse outra razão contextual convincente para isso.
jcalz

1
" Tudo o resto é igual, eu recomendaria o último. " Por que isso? Meu eu pré-datilografado concorda, mas eu sei que o primeiro será mais que um comentário pessoal para as pessoas que vêm do lado do C #, por exemplo, e não é pior para JavaScript-para-Typescripters. Você está interessado apenas em pular a etapa de transpilação para essas construções?
Ruffin

1
Apenas minha opinião: o comportamento de Record<string, V>apenas faz sentido se você já sabe como as assinaturas de índice funcionam no TypeScript. Por exemplo, dado x: Record<string, string>, x.fooaparentemente será um stringtempo de compilação, mas na realidade é provável que seja string | undefined. Essa é uma lacuna na maneira como --strictNullChecksfunciona (consulte # 13778 ). Eu prefiro ter os recém-chegados lidar com {[x: string]: V}diretamente, em vez de esperar que eles sigam a corrente do Record<string, V>meio {[P in string]: V}para o comportamento assinatura índice.
jcalz

Eu queria salientar que logicamente um tipo pode ser definido como um conjunto de todos os valores contidos no tipo. dada essa interpretação, acho que Record <string, V> é razoável como uma abstração para simplificar o código em vez de definir todos os valores possíveis. É semelhante ao exemplo na documentação dos tipos de utilitários: Registre <T, V> em que tipo T = 'a' | 'b' 'c'. Nunca fazer Record <'a', string> não é um bom exemplo de contador, porque não segue o mesmo padrão. Também não é necessário reutilizar ou simplificar o código por meio de abstração, como os outros exemplos.
Scott Leonard

69

Um registro permite criar um novo tipo a partir de uma união. Os valores na União são usados ​​como atributos do novo tipo.

Por exemplo, digamos que eu tenho uma União como esta:

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

Agora, quero criar um objeto que contenha informações sobre todos os gatos. Posso criar um novo tipo usando os valores na União CatName como chaves.

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

Se eu quiser satisfazer esta CatList, devo criar um objeto como este:

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

Você obtém segurança de tipo muito forte:

  • Se eu esquecer um gato, recebo um erro.
  • Se eu adicionar um gato que não é permitido, recebo um erro.
  • Se eu mudar posteriormente os nomes de gato, recebo um erro. Isso é especialmente útil porque CatNames provavelmente é importado de outro arquivo e provavelmente usado em muitos lugares.

Exemplo de reação do mundo real.

Eu usei isso recentemente para criar um componente Status. O componente receberia um suporte de status e renderizaria um ícone. Eu simplifiquei bastante o código aqui para fins ilustrativos

Eu tive uma união assim:

type Statuses = "failed" | "complete";

Eu usei isso para criar um objeto como este:

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

Eu poderia renderizar destruindo um elemento do objeto em adereços, assim:

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

Se a união de status for posteriormente estendida ou alterada, sei que meu componente Status falhará na compilação e receberei um erro que posso corrigir imediatamente. Isso me permite adicionar estados de erro adicionais ao aplicativo.

Observe que o aplicativo real tinha dezenas de estados de erro que foram referenciados em vários locais; portanto, esse tipo de segurança foi extremamente útil.


Presumo que na maioria das vezes type Statusesviva em tipografias NÃO definidas por você? Caso contrário, eu posso ver algo como uma interface com um enum sendo um ajuste melhor, certo?
Victorio Berra

Oi @victorio, não tenho certeza de como uma enumeração resolveria o problema; você não receberá um erro em uma enumeração se perder uma chave. É apenas um mapeamento entre chaves e valores.
superluminar

1
Entendo o que você quer dizer agora. Vindo de C #, não temos maneiras inteligentes de fazer isso. O mais próximo seria um dicionário de Dictionary<enum, additional_metadata>. O tipo Record é uma ótima maneira de representar esse padrão de enumeração + metadados.
Victorio Berra
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.