Por que ir além do básico?
string, number, boolean... todo mundo conhece. Mas o sistema de tipos do TypeScript é muito mais poderoso. Quando você domina as features avançadas, erros que antes só apareciam em produção passam a ser capturados em tempo de compilação.
Mapped Types
Mapped Types permitem transformar cada propriedade de um tipo existente:
type User = {
name: string;
email: string;
age: number;
};
// Torna todas as propriedades opcionais
type Partial<T> = {
[K in keyof T]?: T[K];
};
// Torna todas as propriedades readonly
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
// Exemplo prático: formulário de atualização
type UpdateUserDTO = Partial<User>; // todas opcionaisIndo além — criar variantes com modificadores:
// Remove opcional de todas as propriedades
type Required<T> = {
[K in keyof T]-?: T[K];
};
// Remove readonly de todas as propriedades
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};Template Literal Types
Combinam string literals para criar novos tipos dinamicamente:
type EventName = 'click' | 'focus' | 'blur';
type HandlerName = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"
type CSSProperty = 'margin' | 'padding';
type CSSDirection = 'Top' | 'Right' | 'Bottom' | 'Left';
type CSSShorthand = `${CSSProperty}${CSSDirection}`;
// "marginTop" | "marginRight" | ... | "paddingLeft"Caso de uso real — tipando rotas de API:
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type APIVersion = 'v1' | 'v2';
type Resource = 'users' | 'posts' | 'comments';
type Endpoint = `/${APIVersion}/${Resource}`;
// "/v1/users" | "/v1/posts" | "/v2/users" | ...
function request(method: HTTPMethod, endpoint: Endpoint) {
return fetch(endpoint, { method });
}
request('GET', '/v1/users'); // ✅
request('GET', '/v3/users'); // ❌ Erro de compilação!Conditional Types
Tipos que se comportam como expressões ternárias:
type IsArray<T> = T extends any[] ? true : false;
type A = IsArray<string[]>; // true
type B = IsArray<string>; // falseO poder real está com infer — extrair tipos internos:
// Extrair o tipo do elemento de um array
type ElementType<T> = T extends (infer E)[] ? E : never;
type Numbers = ElementType<number[]>; // number
type Strings = ElementType<string[]>; // string
// Extrair o tipo de retorno de uma função
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
async function fetchUser(): Promise<{ id: string; name: string }> {
// ...
}
type User = Awaited<ReturnType<typeof fetchUser>>;
// { id: string; name: string }Discriminated Unions
O pattern mais poderoso para modelar estados:
type LoadingState = { status: 'loading' };
type SuccessState<T> = { status: 'success'; data: T };
type ErrorState = { status: 'error'; message: string };
type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState;
function renderUser(state: AsyncState<User>) {
switch (state.status) {
case 'loading':
return <Spinner />;
case 'success':
return <div>{state.data.name}</div>; // TypeScript sabe que data existe
case 'error':
return <div>{state.message}</div>; // TypeScript sabe que message existe
}
}Utility Types Menos Conhecidos
// Extract — filtra tipos de uma union
type Animals = 'cat' | 'dog' | 'fish' | 'bird';
type Mammals = Extract<Animals, 'cat' | 'dog' | 'whale'>;
// "cat" | "dog"
// Exclude — remove tipos de uma union
type NonFish = Exclude<Animals, 'fish'>;
// "cat" | "dog" | "bird"
// Parameters — extrai os parâmetros de uma função
function createUser(name: string, age: number, role: 'admin' | 'user') {}
type CreateUserParams = Parameters<typeof createUser>;
// [name: string, age: number, role: "admin" | "user"]Um Type Guard Útil
function isError(value: unknown): value is Error {
return value instanceof Error;
}
function isNonNull<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
// Filtrando nulls de um array
const maybeUsers: (User | null)[] = [...];
const users: User[] = maybeUsers.filter(isNonNull);
// TypeScript infere corretamente que users é User[]Conclusão
O sistema de tipos do TypeScript é uma linguagem dentro de uma linguagem. Investir em aprendê-lo bem significa menos bugs em produção, refatorações mais seguras e uma experiência de desenvolvimento muito mais agradável.
Comece com Mapped Types e Discriminated Unions — eles sozinhos já resolvem 80% dos casos de uso avançados que você vai encontrar no dia a dia.