Conditional & Mapped Types
Baixar PDFTernários em tipos, infer, mapped types e tipos recursivos — programação de tipos completa.
Conditional & Mapped Types
Conditional types e mapped types são o que transforma TypeScript de "JavaScript com anotações" em uma linguagem de tipos Turing-complete. Com eles, você pode derivar, transformar e validar tipos de formas que nenhum outro sistema de tipos mainstream permite.
Conditional Types — extends ? :
Um conditional type é um ternário no nível de tipos. Se A estende B, retorna X; senão, retorna Y.
// Sintaxe: T extends U ? X : Y
type IsString<T> = T extends string ? 'sim' : 'não';
type Test1 = IsString<string>; // 'sim'
type Test2 = IsString<number>; // 'não'
type Test3 = IsString<'hello'>; // 'sim' (string literal extends string)Distribuição sobre unions
Quando T é uma union, o conditional type distribui — aplica em cada membro separadamente:
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>;
// string[] | number[] (NÃO Array<string | number>)
// Para NÃO distribuir, envolva em colchetes:
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type Result2 = ToArrayNonDist<string | number>;
// Array<string | number>Extração de tipo de promise
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<Promise<User>>; // User
type C = UnwrapPromise<number>; // number (não é Promise, retorna como está)infer — Extrair tipos de dentro de outros tipos
infer permite declarar uma variável de tipo dentro de um conditional e extraí-la de estruturas complexas.
Extrair tipo de retorno de função
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type A = GetReturnType<(x: number) => string>; // string
type B = GetReturnType<typeof getUser>; // User (se getUser retornar User)Extrair tipo de array
type ElementType<T> = T extends (infer U)[] ? U : never;
type A = ElementType<string[]>; // string
type B = ElementType<number[]>; // number
type C = ElementType<User[]>; // User
type D = ElementType<string>; // string (não é array — retorna como está)Extrair parâmetros de Promise
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
// Recursivo — lida com Promise<Promise<string>>
type A = Awaited<Promise<Promise<number>>>; // numberExtrair primeiro e último de tuple
type Head<T> = T extends [infer First, ...any[]] ? First : never;
type Tail<T> = T extends [any, ...infer Rest] ? Rest : never;
type Last<T> = T extends [...any[], infer L] ? L : never;
type A = Head<[1, 2, 3]>; // 1
type B = Tail<[1, 2, 3]>; // [2, 3]
type C = Last<[1, 2, 3]>; // 3Extrair tipo de evento DOM
type EventHandler<T extends string> =
T extends `on${infer Event}` ? Lowercase<Event> : never;
type A = EventHandler<'onClick'>; // 'click'
type B = EventHandler<'onMouseEnter'>; // 'mouseenter'
type C = EventHandler<'onSubmit'>; // 'submit'Mapped Types — Transformar cada propriedade
Um mapped type itera sobre as chaves de um tipo existente e aplica uma transformação.
Sintaxe básica
// { [K in keyof T]: transformação }
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
type ReadonlyUser = Readonly<User>;
// { readonly id: string; readonly name: string; readonly email: string; ... }Adicionar/modificar propriedades
// Adiciona campo 'loading' a cada propriedade
type WithLoading<T> = {
[K in keyof T]: {
value: T[K];
loading: boolean;
};
};
type AsyncUser = WithLoading<Pick<User, 'name' | 'email'>>;
// {
// name: { value: string; loading: boolean };
// email: { value: string; loading: boolean };
// }Remapping com as
// Prefixa todas as chaves com 'set'
type Setters<T> = {
[K in keyof T as `set${Capitalize<K & string>}`]: (value: T[K]) => void;
};
type UserSetters = Setters<Pick<User, 'name' | 'email'>>;
// {
// setName: (value: string) => void;
// setEmail: (value: string) => void;
// }Filtrar propriedades
// Remove propriedades que não são funções
type FunctionProperties<T> = {
[K in keyof T as T[K] extends Function ? K : never]: T[K];
};
// Remove propriedades que são funções
type DataProperties<T> = {
[K in keyof T as T[K] extends Function ? never : K]: T[K];
};
interface UserService {
id: string;
name: string;
getUser: (id: string) => User;
createUser: (data: CreateUserDto) => User;
}
type Methods = FunctionProperties<UserService>;
// { getUser: (id: string) => User; createUser: (data: CreateUserDto) => User; }
type Data = DataProperties<UserService>;
// { id: string; name: string; }Tipos recursivos
Conditional + mapped types permitem recursão — tipos que se referenciam.
DeepReadonly
type DeepReadonly<T> = T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
interface Config {
server: {
host: string;
ports: number[];
};
}
type FrozenConfig = DeepReadonly<Config>;
// {
// readonly server: {
// readonly host: string;
// readonly ports: readonly number[];
// };
// }DeepRequired (remove todos os optionals e nullables)
type DeepRequired<T> = T extends object
? { [K in keyof T]-?: DeepRequired<T[K]> }
: T;
interface FormState {
name?: string;
address?: {
street?: string;
city?: string;
};
}
type CompleteForm = DeepRequired<FormState>;
// { name: string; address: { street: string; city: string; } }Path-based access
type Get<T, K extends string> = K extends `${infer Head}.${infer Tail}`
? Head extends keyof T
? Get<T[Head], Tail>
: never
: K extends keyof T
? T[K]
: never;
interface App {
user: {
profile: {
name: string;
avatar: { url: string };
};
settings: { theme: 'light' | 'dark' };
};
}
type Name = Get<App, 'user.profile.name'>; // string
type Theme = Get<App, 'user.settings.theme'>; // 'light' | 'dark'
type Url = Get<App, 'user.profile.avatar.url'>; // stringExemplo full-stack — API response builder
// Tipos que derivam de definições de API
type APIResponse<T> = {
data: T;
meta: {
total: number;
page: number;
perPage: number;
};
};
type PaginatedParams = {
page?: number;
perPage?: number;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
};
// Gera o tipo de parâmetros automaticamente a partir da rota
type RouteParams<R> = R extends `/api/${string}/:${infer Param}`
? Record<Param, string>
: Record<string, never>;
type UserParams = RouteParams<'/api/users/:id'>; // Record<'id', string>
type OrderParams = RouteParams<'/api/orders/:orderId/items/:itemId'>;
// Record<'orderId' | 'itemId', string>