Transformar o contrato de um modelo
Um painel pode precisar de uma opção booleana por cada ação conhecida. Em vez de repetir manualmente todas as chaves, um tipo mapeado percorre keyof T e associa cada chave ao tipo escolhido. A relação fica explícita: uma alteração no modelo pode aparecer também no contrato derivado. Isso ajuda a encontrar mapas incompletos durante a manutenção.
O mapeamento opera sobre tipos. Não percorre um objeto em execução, não preenche valores predefinidos e não cria os campos em falta. Se um formulário precisa de flags inicialmente falsas, alguém tem de construir esse objeto. A revisão deve observar as duas partes: como o contrato é derivado e como a implementação fornece os valores prometidos.
Modificadores e profundidade
Os modificadores ? e readonly controlam opcionalidade e escrita através do tipo. O prefixo - permite removê-los num mapeamento: -? pode exigir um campo antes opcional, enquanto -readonly permite escrita através da vista resultante. Essas operações não fornecem os dados necessários nem descongelam um objeto que tenha sido congelado em execução.
Um mapeamento readonly superficial conserva T[K] tal como está. Se uma propriedade contém um objeto com campos mutáveis, esses campos continuam mutáveis através desse tipo. Antes de prometer uma configuração imutável, decide se o requisito é apenas impedir substituição de propriedades, impedir escrita aninhada ou separar referências. São contratos diferentes e exigem escolhas explícitas.
Renomear ou excluir chaves
A cláusula as de um tipo mapeado calcula a chave resultante. Pode produzir nomes novos ou never para excluir uma chave. Há uma diferença entre excluir a chave e conservar uma propriedade cujo valor é never: o primeiro caso retira-a da interface; o segundo deixa uma exigência que nenhum valor ordinário consegue satisfazer.
Um tipo que transforma name em getName(): string não implementa o método. Uma assertion aplicada ao objeto original pode fazer o consumidor confiar num método inexistente. Da mesma forma, excluir secret de um tipo não o apaga de um objeto enviado em JSON. A transformação dos dados deve ser implementada e revista separadamente da transformação do contrato.
Selecionar e extrair tipos
Um tipo condicional escreve uma decisão sobre atribuibilidade. Na forma T extends { payload: unknown } ? T["payload"] : never, o ramo verdadeiro já sabe que existe a propriedade exigida e pode consultar o seu tipo. unknown na condição não obriga a perder o tipo concreto do payload. O ramo falso permite representar que esta extração não produz um membro válido.
infer dá um nome a uma parte encontrada na forma comparada. Um padrão de array readonly pode extrair o tipo dos elementos de um array ou de uma tupla sem consumir valores. Escrever o padrão como readonly é relevante quando a API deve aceitar também tuplas readonly. Isto continua a ser uma operação de tipos, sem uma inspeção de arrays recebidos em execução.
Distribuição ou decisão sobre a união inteira
Quando o lado esquerdo da condição é um parâmetro T isolado, uma união pode ser avaliada membro a membro. TextMembers<string | number> conserva string e descarta number através de never. Isto é útil para filtrar uma união. Não significa que o programa tenha filtrado uma lista de valores recebidos.
A forma [T] extends [string] evita essa distribuição. Com string | number, a união inteira não é atribuível a string e o resultado é false. Escolhe a operação segundo a pergunta do contrato: quais membros são textuais, ou todos os membros são textuais? Um erro nesta escolha pode permitir um modelo que o consumidor não consegue tratar por completo.
O caso vazio e a leitura de resultados inesperados
never representa um conjunto sem valores possíveis. Numa condição distribuída aplicada a never, não há membros sobre os quais produzir resultados e o resultado mantém-se never. Numa condição envolvida por tuplas, [never] é atribuível a [string], pelo que essa forma pode selecionar true. Não é uma conversão de never para uma string em execução.
Quando um alias dá um resultado inesperado, substitui mentalmente os tipos por casos pequenos: um membro aceite, um recusado, uma união dos dois e never. Observa onde está o parâmetro isolado e onde aparece uma tupla. Essa leitura ajuda a diagnosticar o contrato sem acrescentar assertions que escondem o problema nas chamadas seguintes.
Um mapa local completo com satisfies
No exemplo, Descriptions exige uma função por cada membro de State. O objeto literal fornece essas funções, e satisfies verifica a relação sem anotar o objeto inteiro com um tipo de valores mais amplo. Retirar blocked deixa de satisfazer o contrato. Acrescentar um estado novo exige rever o mapa, em vez de esperar pelo primeiro acesso sem função.
satisfies foi introduzido no TypeScript 4.9 e é usado aqui na base 7.0. Não é uma garantia geral de ausência de propriedades extra em qualquer objeto estrutural nem um validador de pedidos externos. A questão prática usa um literal local e uma união finita. Os dados externos ainda precisam de validação antes de chamar describe; o exemplo parte de um State já válido.
// TypeScript 7.0; strict: true. No external data in this example.
type State = "queued" | "running" | "blocked";
type Descriptions = { [K in State]: () => string };
const descriptions = {
queued: () => "Waiting for capacity",
running: () => "Work in progress",
blocked: () => "Needs a decision"
} satisfies Descriptions;
type TextMembers<T> = T extends string ? T : never;
type EveryMemberIsText<T> = [T] extends [string] ? true : false;
type Allowed = TextMembers<string | number>;
type AllAllowed = EveryMemberIsText<string | number>;
function describe(state: State): string {
return descriptions[state]();
}Identifica as três funções que existem no valor descriptions. Depois compara Allowed, que filtra membros, com AllAllowed, que verifica a união completa. Os aliases não executam um filtro sobre dados.
Armadilhas comuns
Esperar que um tipo mapeado crie métodos ou remova campos; confundir distribuição com verificação da união completa; tratar satisfies como validador de dados externos.
Tópicos relacionados: Uniões, refinamento de tipos e modelos de estado · Genéricos e relações entre chaves · Dados externos e fronteiras de erro
Derivar tipos evita duplicação de contratos. A correspondência entre esses contratos e os valores continua a exigir implementação e verificações adequadas.
Referência: TypeScript: mapping modifiers · TypeScript 7.0; explicit strict and host configuration