Mapped Types no TypeScript:
Domine Mapped Types no TypeScript. Syntax [K in keyof T], modifiers +/-, key remapping e template literal types, tudo com exemplos práticos.
O Que São Mapped Types
Mapped Types iteram sobre as chaves de um tipo e criam um novo tipo transformando cada propriedade. A syntax básica é: { [K in keyof T]: TipoNovo }. É como um loop for, mas pra tipos.
O keyof T pega todas as chaves de T como union type. O in itera sobre cada membro desse union. Pra cada chave K, você define o tipo do valor. Pode manter o original com T[K], transformar, ou trocar completamente.
Modifiers são o toque final. Você pode adicionar (+) ou remover (-) readonly e ? (opcional) de cada propriedade. É assim que Partial adiciona ? em tudo e Required remove.
Com key remapping (usando as), dá pra renomear as chaves durante o mapeamento. Combine com template literal types e você consegue criar coisas como getters e setters automáticos pra cada propriedade. Sem escrever uma linha a mais.
Como Usar Mapped Types Passo a Passo
Vamos construir a compreensão camada por camada. Cada passo abre possibilidades novas.
type MeuTipo = { [K in keyof T]: T[K] }. Isso cria uma cópia exata do tipo T. Parece inútil, mas é a base de tudo.T[K] pelo tipo que quiser. { [K in keyof T]: string } transforma todas as propriedades em string.{ [K in keyof T]?: T[K] } pra tornar tudo opcional. É exatamente o que Partial faz.{ [K in keyof T]-?: T[K] } pra tornar tudo obrigatório. O -? remove o opcional. É o Required.{ readonly [K in keyof T]: T[K] } pra tornar tudo readonly. E { -readonly [K in keyof T]: T[K] } pra remover readonly.{ [K in keyof T as NovoNome]: T[K] } pra renomear chaves. Combine com template literals pra criar padrões como get${K}.Exemplos Práticos de Mapped Types
Do básico ao avançado, com código que você aplica agora.
Recriando Utility Types com Mapped Types
// Tipo base
interface User {
id: number;
name: string;
email: string;
age: number;
}
// Recriando Partial: adiciona ? em tudo
type MeuPartial<T> = {
[K in keyof T]?: T[K];
};
type PartialUser = MeuPartial<User>;
// { id?: number; name?: string; email?: string; age?: number }
// Recriando Required: remove ? de tudo
type MeuRequired<T> = {
[K in keyof T]-?: T[K];
};
// Recriando Readonly: adiciona readonly em tudo
type MeuReadonly<T> = {
readonly [K in keyof T]: T[K];
};
type ReadonlyUser = MeuReadonly<User>;
// { readonly id: number; readonly name: string; ... }
// Removendo readonly: usa -readonly
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
Transformando Valores das Propriedades
// Transformar todos os valores em boolean
type Flags<T> = {
[K in keyof T]: boolean;
};
type UserFlags = Flags<User>;
// { id: boolean; name: boolean; email: boolean; age: boolean }
// Envolver todos os valores em Promise
type Async<T> = {
[K in keyof T]: Promise<T[K]>;
};
type AsyncUser = Async<User>;
// { id: Promise<number>; name: Promise<string>; ... }
// Tornar todos os valores nullable
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
type NullableUser = Nullable<User>;
// { id: number | null; name: string | null; ... }
// Envolver valores em getter functions
type Getters<T> = {
[K in keyof T]: () => T[K];
};
type UserGetters = Getters<User>;
// { id: () => number; name: () => string; ... }
Key Remapping com as
// Key remapping: renomear chaves com 'as'
// Criar getters automaticos: getName, getEmail, etc.
type AutoGetters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserAutoGetters = AutoGetters<User>;
// {
// getId: () => number;
// getName: () => string;
// getEmail: () => string;
// getAge: () => number;
// }
// Criar setters automaticos
type AutoSetters<T> = {
[K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};
type UserAutoSetters = AutoSetters<User>;
// {
// setId: (value: number) => void;
// setName: (value: string) => void;
// ...
// }
// Prefixar todas as chaves com on (eventos)
type EventHandlers<T> = {
[K in keyof T as `on${Capitalize<string & K>}`]: (value: T[K]) => void;
};
type UserEvents = EventHandlers<User>;
// { onId: (value: number) => void; onName: (value: string) => void; ... }
Filtrando Propriedades com Key Remapping
// Filtrar propriedades: remover chaves retornando never no as
interface MixedObj {
name: string;
age: number;
active: boolean;
tags: string[];
score: number;
}
// Pegar so propriedades do tipo string
type StringProps<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
type OnlyStrings = StringProps<MixedObj>;
// { name: string }
// Pegar so propriedades do tipo number
type NumberProps<T> = {
[K in keyof T as T[K] extends number ? K : never]: T[K];
};
type OnlyNumbers = NumberProps<MixedObj>;
// { age: number; score: number }
// Recriando Pick com mapped type
type MeuPick<T, Keys extends keyof T> = {
[K in Keys]: T[K];
};
type PickedUser = MeuPick<User, 'name' | 'email'>;
// { name: string; email: string }
// Recriando Omit com mapped type + key remapping
type MeuOmit<T, Keys extends keyof T> = {
[K in keyof T as K extends Keys ? never : K]: T[K];
};
type OmittedUser = MeuOmit<User, 'id'>;
// { name: string; email: string; age: number }
Template Literal Types em Mapped Types
// Template literal types combinados com mapped types
// Criar tipo de CSS properties
type CSSSize = 'sm' | 'md' | 'lg' | 'xl';
type CSSProp = 'padding' | 'margin' | 'gap';
type CSSClasses = {
[S in CSSSize as `${CSSProp}-${S}`]: string;
};
// { 'padding-sm': string; 'padding-md': string; 'margin-sm': string; ... }
// Criar API de estado com update handlers
interface FormData {
username: string;
password: string;
remember: boolean;
}
type FormHandlers<T> = {
[K in keyof T as `handle${Capitalize<string & K>}Change`]: (
value: T[K]
) => void;
};
type FormAPI = FormData & FormHandlers<FormData>;
// {
// username: string;
// password: string;
// remember: boolean;
// handleUsernameChange: (value: string) => void;
// handlePasswordChange: (value: string) => void;
// handleRememberChange: (value: boolean) => void;
// }
Mapped types são como um for loop pra tipos. Depois que você entende a syntax, começa a ver oportunidades de uso em todo lugar: formulários, APIs, estado, configurações.
Erros Comuns com Mapped Types
Armadilhas que travam a compilação
Esquecer o & string no key remapping com template literals: quando usa Capitalize<K>, o K pode não ser string (pode ser number ou symbol). Use Capitalize<string & K> pra forçar que K seja string.
Confundir keyof com valores: keyof T retorna as CHAVES do tipo, não os valores. Se T é { name: string; age: number }, keyof T é 'name' | 'age', não string | number.
Modifier - sem +: escrever -? remove o opcional, mas escrever só ? adiciona. O + é implícito: +? é o mesmo que ?. Já pra remover, o - é obrigatório.
Mapped types recursivos sem limite: se o tipo mapeado referencia a si mesmo sem condição de parada, o compilador entra em loop. Sempre use conditional type pra definir quando parar.
Tentar mapear unions primitivos: mapped types só funcionam com tipos de objeto. Pra transformar unions de primitivos, use conditional types. { [K in keyof string]: ... } não faz o que você espera.
Checklist de Mapped Types
Crie Tipos Avançados na Prática
Mapped types são a ferramenta que te coloca no nível avançado de TypeScript. No CrazyStack, você usa mapped types pra criar DTOs automáticos, validações tipadas e APIs type-safe. Tudo num projeto real com Node.js e React do zero ao deploy.
Se você quer parar de copiar tipos manualmente e começar a gerar tipagem automática, esse é o caminho mais direto.
Continue lendo
Como Usar Conditional Types no TypeScript
Domine tipos condicionais no TypeScript com extends, never e distribuição.
Como Usar Utility Types no TypeScript
Domine Partial, Pick, Omit, Record e outros Utility Types do TypeScript.
Como Usar Type no TypeScript
Domine type aliases no TypeScript com exemplos diretos.
Interface no TypeScript
Quando usar interface