// tipos avançados, generics e boas práticas
Referência prática com exemplos interativos de Utility Types, Generics, Type Narrowing e patterns avançados para escrever TypeScript mais seguro e expressivo.
// tipos built-in que todo dev precisa conhecer
TypeScript oferece vários types utilitários globais que facilitam transformações de tipos comuns.
Partial<T>Torna todas as propriedades opcionais. Útil para funções de update parcial.
interface User {name: string;email: string;age: number;}function updateUser(user: User, fields: Partial<User>) {return { ...user, ...fields };}// ✅ Só precisa passar o que quer atualizarupdateUser(user, { name: 'Novo Nome' });
Required<T>O oposto de Partial — torna todas as propriedades obrigatórias.
interface Config {host?: string;port?: number;debug?: boolean;}function startServer(config: Required<Config>) {// Garantia que todos os campos existemconsole.log(`${config.host}:${config.port}`);}
Pick<T, K>Seleciona apenas propriedades específicas de um tipo.
interface Article {id: string;title: string;body: string;author: string;createdAt: Date;}// Só precisa de title e body para o formtype ArticleForm = Pick<Article, 'title' | 'body'>;
Omit<T, K>Remove propriedades específicas de um tipo.
interface User {id: string;name: string;email: string;password: string;}// Remove senha para respostas da APItype PublicUser = Omit<User, 'password'>;
Record<K, T>Cria um tipo de objeto com chaves K e valores T.
type Status = 'idle' | 'loading' | 'success' | 'error';const statusMessages: Record<Status, string> = {idle: 'Aguardando...',loading: 'Carregando...',success: 'Concluído!',error: 'Erro ao processar',};
Extract / ExcludeExtract mantém tipos atribuíveis, Exclude remove.
type Event = 'click' | 'scroll' | 'mousemove' | 'keypress';// Apenas eventos de mousetype MouseEvent = Extract<Event, 'click' | 'mousemove'>;// → 'click' | 'mousemove'// Tudo exceto tecladotype NonKeyEvent = Exclude<Event, 'keypress'>;// → 'click' | 'scroll' | 'mousemove'
// tipos reutilizáveis e type-safe
Generics permitem criar componentes, funções e tipos que funcionam com qualquer tipo, mantendo type safety.
O tipo é inferido automaticamente a partir dos argumentos.
function firstElement<T>(arr: T[]): T | undefined {return arr[0];}// TypeScript infere o tipo de retornoconst num = firstElement([1, 2, 3]); // numberconst str = firstElement(['a', 'b']); // string
Restrinja o tipo genérico para garantir que tenha certas propriedades.
interface HasLength {length: number;}function logLength<T extends HasLength>(item: T): T {console.log(item.length);return item;}logLength('hello'); // ✅ string tem lengthlogLength([1, 2, 3]); // ✅ array tem length// logLength(123); // ❌ number não tem length
Combine keyof com generics para acessar propriedades de forma type-safe.
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {return obj[key];}const user = { name: 'Ana', age: 28 };const name = getProperty(user, 'name'); // stringconst age = getProperty(user, 'age'); // number// getProperty(user, 'email'); // ❌ Erro!
Defina um tipo padrão para quando o genérico não for especificado.
interface ApiResponse<T = unknown> {data: T;status: number;message: string;}// Com tipo específicoconst userRes: ApiResponse<User> = { ... };// Sem especificar (usa unknown)const genericRes: ApiResponse = { ... };
Crie novos tipos transformando propriedades de tipos existentes.
type Getters<T> = {[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];};interface Person {name: string;age: number;}type PersonGetters = Getters<Person>;// { getName: () => string; getAge: () => number; }
// refinando tipos em runtime
Type Narrowing é o processo de refinar tipos de um tipo mais amplo para um mais específico usando verificações em runtime.
O guard mais básico — verifica tipos primitivos.
function process(value: string | number) {// value é string | numberconsole.log(value.toUpperCase());// ❌ Erro: toUpperCase não existe em number}
Pattern poderoso usando uma propriedade literal para distinguir tipos.
interface Shape {kind: string;radius?: number;width?: number;height?: number;}// Propriedades opcionais geram incertezafunction area(shape: Shape) {if (shape.kind === 'circle') {return Math.PI * shape.radius! ** 2; // ! é perigoso}}
Crie suas próprias funções de type guard customizadas.
interface Fish { swim: () => void }interface Bird { fly: () => void }// Sem type predicate, TS não sabe qual tipo éfunction move(animal: Fish | Bird) {if ('swim' in animal) {animal.swim(); // funciona, mas não é ideal}}
Garanta que todos os casos de uma union foram tratados em compile time.
type Status = 'active' | 'inactive' | 'banned';function getLabel(status: Status) {switch (status) {case 'active': return 'Ativo';case 'inactive': return 'Inativo';// Esqueceu 'banned' — sem erro!}}
// técnicas para projetos reais
Patterns e técnicas avançadas de TypeScript usados em projetos profissionais.
Combine string literals para criar tipos derivados automaticamente.
type EventName = 'click' | 'focus' | 'blur';type Handler = `on${Capitalize<EventName>}`;// 'onClick' | 'onFocus' | 'onBlur'type CSSProperty = 'margin' | 'padding';type Direction = 'Top' | 'Right' | 'Bottom' | 'Left';type CSSKey = `${CSSProperty}${Direction}`;// 'marginTop' | 'marginRight' | ... 8 combinações
Tipos que se comportam como if/else baseado em condições.
type IsString<T> = T extends string ? true : false;type A = IsString<'hello'>; // truetype B = IsString<42>; // false// Exemplo prático: extrair tipo de retorno de Promisetype UnwrapPromise<T> = T extends Promise<infer U> ? U : T;type Result = UnwrapPromise<Promise<string>>; // stringtype Plain = UnwrapPromise<number>; // number
Extraia tipos de dentro de outros tipos usando infer.
// Extrair tipo dos elementos de um arraytype ElementOf<T> = T extends (infer E)[] ? E : never;type Num = ElementOf<number[]>; // number// Extrair tipo dos parâmetros de uma funçãotype FirstParam<F> = F extends (arg: infer P, ...args: unknown[]) => unknown? P: never;type Param = FirstParam<(name: string, age: number) => void>;// string
Use generics para criar APIs fluentes com tipos que evoluem.
class QueryBuilder<T extends object = object> {private filters: Partial<T> = {};where<K extends keyof T>(key: K, value: T[K]) {this.filters[key] = value;return this;}build() {return this.filters;}}// Uso type-safenew QueryBuilder<User>().where('name', 'Ana') // ✅.where('age', 28) // ✅// .where('age', 'abc') // ❌ Erro!.build();
Crie tipos nominais para evitar misturar valores que são estruturalmente iguais.
type Brand<T, B> = T & { __brand: B };type UserId = Brand<string, 'UserId'>;type OrderId = Brand<string, 'OrderId'>;function getUser(id: UserId) { /* ... */ }function getOrder(id: OrderId) { /* ... */ }const userId = 'u_123' as UserId;const orderId = 'o_456' as OrderId;getUser(userId); // ✅// getUser(orderId); // ❌ Tipo incompatível!