Advanced React & TypeScript Patterns
🧱 1. Padrões de Tipagem Estrutural (Structural Type Patterns)
1.1 Discriminated Unions (Uniões Discriminadas)
Usado para modelar estados mutuamente exclusivos com segurança.
type LoadingState = { status: 'loading' };
type SuccessState<T> = { status: 'success'; data: T };
type ErrorState = { status: 'error'; error: Error };
type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState;
// Uso com type narrowing automático
function handleState<T>(state: AsyncState<T>) {
if (state.status === 'loading') {
console.log('Carregando...');
} else if (state.status === 'success') {
console.log(state.data); // T disponível
} else {
console.log(state.error.message);
}
}
1.2 Branded / Opaque Types (Tipos Nominais)
Criam tipos que são estruturalmente idênticos, mas semanticamente distintos, evitando confusões entre IDs.
type UserId = string & { readonly __brand: unique symbol };
type ProductId = string & { readonly __brand: unique symbol };
function createUserId(id: string): UserId {
return id as UserId;
}
function getUser(id: UserId) { /* ... */ }
// getUser(createProductId('123')); // Erro de tipo!
1.3 Satisfies Operator
Valida o tipo de uma expressão sem alargá-lo (mantém a inferência mais específica).
const config = {
port: 3000,
env: 'production',
logLevel: 'info'
} satisfies Record<string, string | number>;
// config.port é `number`, não `string | number`
// config.env é `'production'`, não `string`
🧩 2. Padrões de Manipulação de Tipos (Type Manipulation Patterns)
2.1 Conditional Types com infer
Extraem tipos de dentro de outros tipos. Útil para utilitários avançados.
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Result = UnwrapPromise<Promise<string>>; // string
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Fn = (x: number) => string;
type R = GetReturnType<Fn>; // string
2.2 Mapped Types com Modificadores (+?, -?, readonly)
Criam novos tipos a partir de chaves existentes.
// Tornar todas as propriedades opcionais e readonly
type PartialReadonly<T> = {
readonly [K in keyof T]?: T[K];
};
// Remover o readonly de todas as propriedades
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
// Pick por tipo de valor (ex: pegar apenas strings)
type PickStrings<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
2.3 Template Literal Types
Criam tipos baseados em strings, perfeitos para rotas, eventos e DOM.
type EventType = 'click' | 'hover' | 'focus';
type HandlerName = `on${Capitalize<EventType>}`; // 'onClick' | 'onHover' | 'onFocus'
// Validação de rotas com parâmetros
type Route = `/user/${string}/post/${string}`;
const route: Route = '/user/123/post/456'; // OK
2.4 Variadic Tuple Types
Permitem manipular tuplas de tamanho variável, essencial para composição de funções.
// Concatenar tuplas
type Concat<T extends any[], U extends any[]> = [...T, ...U];
type Result = Concat<[1, 2], [3, 4]>; // [1, 2, 3, 4]
// Função que recebe e espalha argumentos
declare function spread<T extends any[]>(...args: T): T;
const arr = spread(1, 'a', true); // tipo: [number, string, boolean]
🏗️ 3. Design Patterns Clássicos com TypeScript
3.1 Factory Pattern (Fábrica) com Generics
Cria instâncias com tipagem inferida automática.
interface Product {
id: string;
name: string;
}
class ProductFactory {
create<T extends Product>(type: new () => T): T {
return new type();
}
}
class Book implements Product {
id = 'b1';
name = 'Book';
author = 'Unknown';
}
const factory = new ProductFactory();
const book = factory.create(Book); // tipo: Book (com author disponível)
3.2 Builder Pattern (Construtor) com Estado Tipado
Garante que métodos sejam chamados na ordem correta via estados.
interface Step1 { setA(value: string): Step2; }
interface Step2 { setB(value: number): Final; }
interface Final { build(): { a: string; b: number }; }
class Builder implements Step1, Step2, Final {
private a = '';
private b = 0;
setA(value: string) { this.a = value; return this; }
setB(value: number) { this.b = value; return this; }
build() { return { a: this.a, b: this.b }; }
}
const result = new Builder().setA('test').setB(10).build();
3.3 Adapter Pattern (Adaptador) com Mapeamento de Tipos
Adapta dados externos (API) para o domínio interno com validação.
interface ApiUser {
user_id: string;
full_name: string;
age_str: string;
}
interface DomainUser {
id: string;
name: string;
age: number;
}
function adaptUser(api: ApiUser): DomainUser {
return {
id: api.user_id,
name: api.full_name,
age: parseInt(api.age_str, 10)
};
}
// Uso com `satisfies` para garantir que o adaptador cobre todas as chaves
🧪 4. Padrões para Funcionalidade e Utilitários
4.1 Result / Either Pattern (Tratamento de Erros)
Representa operações que podem falhar sem lançar exceções.
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
function safeParse<T>(json: string): Result<T> {
try {
return { ok: true, value: JSON.parse(json) };
} catch (error) {
return { ok: false, error: error as Error };
}
}
const res = safeParse<{ id: number }>('{"id":1}');
if (res.ok) console.log(res.value.id);
4.2 Curry (Currying) Tipado com Variadic Tuples
Função que transforma (a, b, c) => R em (a) => (b) => (c) => R.
type Curry<P extends any[], R> =
P extends [infer First, ...infer Rest]
? (arg: First) => Curry<Rest, R>
: R;
declare function curried<A extends any[], R>(fn: (...args: A) => R): Curry<A, R>;
const sum = (a: number, b: number, c: number) => a + b + c;
const curriedSum = curried(sum);
const result = curriedSum(1)(2)(3); // 6
4.3 Registry Pattern (Registro de Serviços)
Mantém um mapa de construtores ou funções com chaves tipadas via Record.
interface ServiceMap {
user: { getName(): string };
product: { getPrice(): number };
}
class ServiceRegistry {
private services: Partial<Record<keyof ServiceMap, ServiceMap[keyof ServiceMap]>> = {};
register<K extends keyof ServiceMap>(key: K, service: ServiceMap[K]) {
this.services[key] = service;
}
get<K extends keyof ServiceMap>(key: K): ServiceMap[K] {
if (!this.services[key]) throw new Error('Service not found');
return this.services[key] as ServiceMap[K];
}
}
🔌 5. Padrões de Declaração e Módulos
5.1 Module Augmentation (Aumento de Módulo)
Estende definições de terceiros ou globais.
// Augmentando o Array global
declare global {
interface Array<T> {
last(): T | undefined;
}
}
if (!Array.prototype.last) {
Array.prototype.last = function() {
return this[this.length - 1];
};
}
5.2 Declaration Merging (Fusão de Declarações)
Combina múltiplas declarações de mesma interface/namespace.
interface Config {
port: number;
}
interface Config {
host: string;
}
// Resultado: Config { port: number; host: string; }
// Útil para bibliotecas com plugins
namespace Validation {
export interface Validator {}
}
namespace Validation {
export interface EmailValidator extends Validator {}
}
🎯 6. Padrões para Hooks e React (Interseção)
Embora o foco seja TypeScript puro, estes padrões são cruciais no ecossistema React:
6.1 Typing Custom Hooks com Overloads
function useState<T>(initial: T): [T, (v: T | ((prev: T) => T)) => void];
function useState<T = undefined>(): [T | undefined, (v: T | ((prev: T) => T)) => void];
// Implementação...
6.2 Higher-Order Components (HOC) com Injeção de Props
type WithAuthProps = { isAuthenticated: boolean };
function withAuth<P extends WithAuthProps>(
Component: React.ComponentType<P>
): React.ComponentType<Omit<P, keyof WithAuthProps>> {
return function Wrapped(props: Omit<P, keyof WithAuthProps>) {
const auth = { isAuthenticated: true };
return <Component {...(props as P)} {...auth} />;
};
}
📋 7. Checklist para Entrevistas Sênior
| Pergunta | Padrão/Conceito cobrado |
|---|---|
| “Como você faria um sistema de eventos onde o nome do evento dita o tipo do payload?” | Template Literals + Mapped Types (ex: EventMap[K]) |
| “Como evitar que IDs de diferentes entidades se misturem?” | Branded Types / Opaque Types |
| “Como criar um utilitário que extrai o tipo de retorno de uma função assíncrona?” | Awaited<ReturnType<T>> + Conditional Types |
“Como você implementaria um Redux reducer seguro sem any?” |
Discriminated Unions para ações (type + payload) |
| “Como garantir que um objeto config tenha todas as chaves de um enum, mas permita valores adicionais?” | Record<Enum, T> & Partial<Record<string, unknown>> |
“Como tipar um compose de funções com número variável de argumentos?” |
Variadic Tuple Types + infer recursivo |
🧠 Desafio Prático Final
Construa uma função createApiClient que:
- Recebe uma URL base e um objeto de endpoints.
- Cada endpoint tem método (GET/POST) e path com parâmetros (ex:
/users/:id). - Retorna um cliente onde cada endpoint é um método que aceita os parâmetros e o body (se POST) e retorna uma Promise tipada.
- Use Template Literal Types para extrair os parâmetros do path.
- Use Conditional Types para definir o body obrigatório apenas para POST.
📚 Referências e Ferramentas
ts-reset– Corrige tipos nativos problemáticos.zod– Validação em runtime com inferência de tipos estáticos.type-fest– Coleção de utility types avançados (ex:OmitIndexSignature,DelimiterCasedProperties).- ESLint:
@typescript-eslint/consistent-type-imports– Forçaimport typepara melhor bundling.
—typeScript-patterns
Conclusão: Dominar estes padrões não é apenas sobre escrever tipos, mas sobre modelar domínios, prevenir bugs em tempo de compilação e criar APIs intuitivas. O verdadeiro poder do TypeScript emerge quando os tipos guiam a implementação, e não apenas a documentam.