Conditional Types no TypeScript: Guia
Domine tipos condicionais no TypeScript. Syntax T extends U ? X : Y, distribuição, filtros com never e infer, tudo com exemplos práticos.
Por que isso é importante
Conditional Types no TypeScript: Guia. Domine tipos condicionais no TypeScript. Syntax T extends U ? X : Y, distribuição, filtros com never e infer, tudo com exemplos práticos.
O Que São Conditional Types
Conditional Types são tipos que mudam dependendo de uma condição. A syntax é parecida com o ternário do JavaScript: T extends U ? X : Y. Se T é compatível com U, o resultado é X. Senão, é Y.
O extends aqui NÃO significa herança. Significa 'é atribuível a'. Quando você escreve string extends object, tá perguntando: 'dá pra atribuir string onde espera object?'. A resposta define qual branch do tipo é usado.
A parte interessante é a distribuição. Quando o T é um union type, o TypeScript aplica a condição pra cada membro separadamente. Isso é o que torna conditional types tão poderosos pra filtrar e transformar unions.
Com a keyword infer, você ainda consegue extrair partes de um tipo dentro da condição. Por exemplo: pegar o tipo de retorno de uma função, ou o tipo dos elementos de um array. Tudo em tempo de compilação.
Como Usar Conditional Types Passo a Passo
Vamos do básico ao avançado. Cada passo constrói em cima do anterior.
type IsString<T> = T extends string ? 'sim' : 'nao'. Isso cria um tipo que retorna 'sim' se T for string, 'nao' se não for.extends em conditional types verifica compatibilidade. number extends object é false. string extends string | number é true.string | number pra IsString<T>, o resultado é 'sim' | 'nao' porque cada membro é avaliado separadamente.never, ele é removido do union. Isso é como filtrar: T extends string ? T : never mantém só strings.T extends Array<infer U> ? U : never extrai o tipo dos elementos do array. infer captura um tipo dentro da condição.T extends string ? A : T extends number ? B : C. Como if/else if/else pra tipos.Exemplos Práticos de Conditional Types
Vamos ver código que mostra o poder real dos tipos condicionais.
Conditional Type Básico
// Syntax basica: T extends U ? TrueType : FalseType
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
type C = IsString<'hello'>; // true (literal string extends string)
// Retornando tipos diferentes baseado na condicao
type TypeName<T> =
T extends string ? 'string' :
T extends number ? 'number' :
T extends boolean ? 'boolean' :
T extends Function ? 'function' :
'object';
type T1 = TypeName<string>; // 'string'
type T2 = TypeName<() => void>; // 'function'
type T3 = TypeName<string[]>; // 'object'
Distribuição: O Superpoder dos Conditional Types
// Quando T e um union, a condicao aplica pra cada membro
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>;
// Distribui: ToArray<string> | ToArray<number>
// Resultado: string[] | number[]
// NAO e (string | number)[] !!
// Pra desligar distribuicao, envolva com colchetes
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type Result2 = ToArrayNonDist<string | number>;
// Resultado: (string | number)[]
// Filtragem com never - like Exclude
type OnlyStrings<T> = T extends string ? T : never;
type Filtered = OnlyStrings<'a' | 1 | 'b' | true | 'c'>;
// Resultado: 'a' | 'b' | 'c'
// O never some do union automaticamente
Filtrando com Never: Como Exclude e Extract Funcionam
// Recriando Exclude do zero
type MeuExclude<T, U> = T extends U ? never : T;
type Status = 'loading' | 'success' | 'error' | 'idle';
type ActiveStatus = MeuExclude<Status, 'idle'>;
// Resultado: 'loading' | 'success' | 'error'
// Recriando Extract do zero
type MeuExtract<T, U> = T extends U ? T : never;
type Strings = MeuExtract<string | number | boolean, string>;
// Resultado: string
// Recriando NonNullable do zero
type MeuNonNullable<T> = T extends null | undefined ? never : T;
type SafeValue = MeuNonNullable<string | null | undefined>;
// Resultado: string
// Filtrando eventos por tipo
type AppEvent =
| { kind: 'click'; x: number }
| { kind: 'scroll'; offset: number }
| { kind: 'keypress'; key: string };
type ClickEvent = Extract<AppEvent, { kind: 'click' }>;
// Resultado: { kind: 'click'; x: number }
Infer: Extraindo Tipos Automaticamente
// infer extrai um tipo dentro do conditional
// Pegar tipo dos elementos de um array
type ElementOf<T> = T extends Array<infer U> ? U : never;
type Elem = ElementOf<string[]>; // string
type Elem2 = ElementOf<number[]>; // number
// Pegar tipo de retorno (como ReturnType funciona)
type MeuReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Ret = MeuReturnType<() => string>; // string
type Ret2 = MeuReturnType<(x: number) => boolean>; // boolean
// Pegar tipo do primeiro parametro
type FirstParam<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;
type P = FirstParam<(name: string, age: number) => void>; // string
// Extrair tipo de uma Promise
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
type Val = Awaited<Promise<string>>; // string
type Val2 = Awaited<Promise<Promise<number>>>; // number (recursivo!)
Exemplo Real: API Response Tipada Condicionalmente
// Cenario real: response de API que muda baseado no endpoint
interface Endpoints {
'/users': User[];
'/users/:id': User;
'/posts': Post[];
'/settings': Settings;
}
type ApiResponse<T extends keyof Endpoints> = {
data: Endpoints[T];
status: number;
timestamp: string;
};
// O tipo de data muda automaticamente
async function fetchApi<T extends keyof Endpoints>(
endpoint: T
): Promise<ApiResponse<T>> {
const res = await fetch(endpoint);
return res.json();
}
// TypeScript sabe o tipo do retorno
const users = await fetchApi('/users'); // data: User[]
const settings = await fetchApi('/settings'); // data: Settings
Conditional types transformam o sistema de tipos do TypeScript numa linguagem de programação pra tipos. Você consegue expressar lógica complexa que seria impossível só com interfaces e types simples.
Erros Comuns com Conditional Types
Cuidados que evitam horas de debug
Não entender distribuição: quando T é union, o conditional aplica pra cada membro. IsString<string | number> NÃO é o mesmo que avaliar 'string | number' de uma vez. Se quer desligar distribuição, use [T] extends [U].
Abusar de infer: infer só funciona dentro de conditional types, na posição do branch true. Tentar usar fora de um extends gera erro de compilação.
Recursão infinita: conditional types podem ser recursivos, mas sem caso base o compilador trava. Sempre tenha uma condição de parada clara.
Confundir never em unions: never some automaticamente de unions. Se seu conditional retorna never em todos os branches, o tipo final é never. Isso geralmente indica que nenhum membro do union passou na condição.
Conditional types em genéricos adiados: quando T é um parâmetro genérico não resolvido, o TypeScript NÃO avalia o conditional na hora. Ele adia até T ser conhecido. Isso pode gerar tipos que parecem errados mas estão certos.
Checklist de Conditional Types
Tipagem Avançada na Prática
Conditional types são o nível que separa quem usa TypeScript de quem domina TypeScript. No CrazyStack, você aplica tipos condicionais em APIs, componentes React e validação de dados. Tudo num projeto real que vai do zero ao deploy.
Se você quer parar de depender de any e começar a criar tipos que se adaptam ao contexto, esse é o próximo passo concreto.
Continue lendo
Como Usar Mapped Types no TypeScript
Mapped types com [K in keyof T], modifiers e key remapping.
Como Usar Infer no TypeScript
Domine a keyword infer pra extrair tipos automaticamente no TypeScript.
Como Usar Utility Types no TypeScript
Domine Partial, Pick, Omit, Record e outros Utility Types do TypeScript.
Interface no TypeScript
Quando usar interface