O sistema de tipos do TypeScript é notavelmente poderoso, permitindo padrões que detetam bugs em tempo de compilação em vez de em tempo de execução. Ir além dos tipos básicos para padrões avançados melhora drasticamente a qualidade do código e a experiência do programador. Este guia aborda padrões de TypeScript que todos os programadores sénior devem dominar.
Porquê TypeScript Avançado
Dominar TypeScript avançado permite:
- Segurança em Tempo de Compilação: Detetar erros antes de chegarem à produção
- Melhor IntelliSense: O IDE conhece a estrutura do seu código
- Código Auto-Documentado: Os tipos descrevem a intenção
- Confiança na Refatoração: O verificador de tipos deteta alterações incompatíveis
- Design de API: Expressar restrições no sistema de tipos
Tipos Utilitários
Tipos Utilitários Integrados
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
// Partial: Todas as propriedades opcionais
type UserUpdate = Partial<User>;
// { id?: number; name?: string; email?: string; ... }
// Required: Todas as propriedades obrigatórias
type CompleteUser = Required<Partial<User>>;
// Pick: Selecionar propriedades específicas
type UserPreview = Pick<User, 'id' | 'name'>;
// { id: number; name: string; }
// Omit: Excluir propriedades específicas
type UserWithoutPassword = Omit<User, 'password'>;
// { id: number; name: string; email: string; createdAt: Date; }
// Readonly: Todas as propriedades apenas de leitura
type ImmutableUser = Readonly<User>;
// Não é possível reatribuir propriedades
// Record: Criar tipo de objeto com chaves e tipo de valor
type UserRoles = Record<string, 'admin' | 'user' | 'guest'>;
// { [key: string]: 'admin' | 'user' | 'guest' }
// Extract: Extrair tipos de uma união
type StringOrNumber = string | number | boolean;
type OnlyStrings = Extract<StringOrNumber, string>;
// string
// Exclude: Remover tipos de uma união
type NotString = Exclude<StringOrNumber, string>;
// number | boolean
// NonNullable: Remover null e undefined
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>;
// string
// ReturnType: Obter o tipo de retorno da função
function createUser() {
return { id: 1, name: 'John' };
}
type NewUser = ReturnType<typeof createUser>;
// { id: number; name: string; }
// Parameters: Obter os tipos dos parâmetros da função
function greet(name: string, age: number) {}
type GreetParams = Parameters<typeof greet>;
// [string, number]
Genéricos
Genéricos Básicos
// Função genérica
function identity<T>(value: T): T {
return value;
}
const num = identity(42); // number
const str = identity('hello'); // string
// Interface genérica
interface Container<T> {
value: T;
getValue(): T;
}
// Classe genérica
class Box<T> {
constructor(private content: T) {}
getContent(): T {
return this.content;
}
}
const numberBox = new Box(123);
const stringBox = new Box('hello');
Genéricos com Restrições
// Restrição com extends
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): void {
console.log(item.length);
}
logLength('hello'); // OK
logLength([1, 2, 3]); // OK
logLength({ length: 5 }); // OK
// logLength(123); // Erro: number não tem length
// Múltiplas restrições
interface HasId {
id: number;
}
interface HasName {
name: string;
}
function merge<T extends HasId, U extends HasName>(a: T, b: U): T & U {
return { ...a, ...b };
}
// Restrição com keyof
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: 'John', age: 30 };
const name = getProperty(user, 'name'); // string
const age = getProperty(user, 'age'); // number
// getProperty(user, 'invalid'); // Erro
Tipos Genéricos por Omissão
interface ApiResponse<T = unknown> {
data: T;
status: number;
message: string;
}
// Usa o valor por omissão
const response: ApiResponse = { data: {}, status: 200, message: 'OK' };
// Tipo explícito
const userResponse: ApiResponse<User> = {
data: { id: 1, name: 'John', email: '[email protected]', password: '', createdAt: new Date() },
status: 200,
message: 'OK'
};
Tipos Condicionais
Tipos Condicionais Básicos
// T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// Exemplo prático: desembrulhar Promise
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type PromiseString = Promise<string>;
type Unwrapped = Unwrap<PromiseString>; // string
type NotPromise = Unwrap<number>; // number
// Tipo de elemento do array
type ArrayElement<T> = T extends (infer E)[] ? E : never;
type StringArrayElement = ArrayElement<string[]>; // string
Tipos Condicionais Distributivos
type ToArray<T> = T extends any ? T[] : never;
// Distribui sobre a união
type StringOrNumberArray = ToArray<string | number>;
// string[] | number[]
// Evitar distribuição com []
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type Combined = ToArrayNonDist<string | number>;
// (string | number)[]
Tipos Mapeados
Tipos Mapeados Básicos
// Tornar todas as propriedades opcionais
type Optional<T> = {
[K in keyof T]?: T[K];
};
// Tornar todas as propriedades obrigatórias
type Required<T> = {
[K in keyof T]-?: T[K];
};
// Tornar todas as propriedades apenas de leitura
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
// Remover apenas de leitura
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
Tipos Mapeados Avançados
// Transformar tipos de propriedades
type Stringify<T> = {
[K in keyof T]: string;
};
// Filtrar propriedades por tipo de valor
type OnlyStrings<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
interface Mixed {
name: string;
age: number;
email: string;
}
type StringProps = OnlyStrings<Mixed>;
// { name: string; email: string; }
// Renomear propriedades
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; }
Tipos de Literais de Template
Literais de Template Básicos
type Greeting = `Hello, ${string}!`;
const valid: Greeting = 'Hello, World!'; // OK
// const invalid: Greeting = 'Hi, World!'; // Erro
// Combinar uniões
type Color = 'red' | 'green' | 'blue';
type Size = 'small' | 'medium' | 'large';
type ColoredSize = `${Color}-${Size}`;
// 'red-small' | 'red-medium' | 'red-large' | 'green-small' | ...
// Métodos HTTP
type Method = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Endpoint = '/users' | '/posts';
type Route = `${Method} ${Endpoint}`;
Tipos de Manipuladores de Eventos
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<'click'>; // 'onClick'
type ChangeEvent = EventName<'change'>; // 'onChange'
// Criar tipo de manipulador de eventos
type EventHandlers<T> = {
[K in keyof T as EventName<string & K>]: (value: T[K]) => void;
};
interface FormFields {
name: string;
email: string;
age: number;
}
type FormHandlers = EventHandlers<FormFields>;
// { onName: (value: string) => void; onEmail: (value: string) => void; onAge: (value: number) => void; }
Uniões Discriminadas
Correspondência de Padrões
interface Loading {
status: 'loading';
}
interface Success<T> {
status: 'success';
data: T;
}
interface Error {
status: 'error';
error: string;
}
type AsyncState<T> = Loading | Success<T> | Error;
function handleState<T>(state: AsyncState<T>): string {
switch (state.status) {
case 'loading':
return 'Loading...';
case 'success':
return `Data: ${JSON.stringify(state.data)}`;
case 'error':
return `Error: ${state.error}`;
}
}
// Verificação de exaustividade
function assertNever(x: never): never {
throw new Error(`Unexpected value: ${x}`);
}
function handleStateExhaustive<T>(state: AsyncState<T>): string {
switch (state.status) {
case 'loading':
return 'Loading...';
case 'success':
return `Data: ${JSON.stringify(state.data)}`;
case 'error':
return `Error: ${state.error}`;
default:
return assertNever(state); // Erro de compilação se faltar um caso
}
}
Guardas de Tipo
Guardas de Tipo Personalizados
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
// Predicado de tipo
function isCat(animal: Cat | Dog): animal is Cat {
return 'meow' in animal;
}
function makeSound(animal: Cat | Dog) {
if (isCat(animal)) {
animal.meow(); // O TypeScript sabe que é Cat
} else {
animal.bark(); // O TypeScript sabe que é Dog
}
}
// Função de asserção (lança se for falso)
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new Error('Value is not a string');
}
}
function processValue(value: unknown) {
assertIsString(value);
// O TypeScript sabe que o valor é string aqui
console.log(value.toUpperCase());
}
Fusão de Declarações
Estender Tipos Existentes
// Estender Window
declare global {
interface Window {
analytics: {
track: (event: string, data?: object) => void;
};
}
}
// Agora é válido
window.analytics.track('pageview');
// Estender Express Request
declare namespace Express {
interface Request {
user?: {
id: string;
role: string;
};
}
}
// Estender módulo
declare module 'express' {
interface Request {
customProperty: string;
}
}
Padrões Práticos
Padrão Builder com Tipos
class RequestBuilder<T extends object = {}> {
private config: T = {} as T;
withUrl<U extends string>(url: U): RequestBuilder<T & { url: U }> {
return Object.assign(this, { config: { ...this.config, url } });
}
withMethod<M extends 'GET' | 'POST'>(method: M): RequestBuilder<T & { method: M }> {
return Object.assign(this, { config: { ...this.config, method } });
}
build(): T {
return this.config;
}
}
const request = new RequestBuilder()
.withUrl('/api/users')
.withMethod('GET')
.build();
// Tipo: { url: '/api/users'; method: 'GET'; }
Principais Conclusões
- Use tipos utilitários: Não reinvente Partial, Pick, Omit
- Restrinja genéricos:
extends torna os tipos mais úteis - Uniões discriminadas: Correspondência de padrões poderosa
- Guardas de tipo: Restringir tipos em segurança
- Literais de template: Manipulação de strings com segurança de tipos
- Tipos mapeados: Transformar estruturas de tipos de forma sistemática
O sistema de tipos do TypeScript é uma linguagem dentro de uma linguagem — invista em aprendê-lo a fundo e a qualidade do seu código irá melhorar drasticamente.