Programação Orientada a Objetos (POO) no TS Web
No desenvolvimento Front-end moderno, o paradigma funcional domina com React Hooks. No entanto, os conceitos da POO são fundamentais para organizar lógica de negócio e infraestrutura. No nosso projeto, evitamos o uso de classes (exceto para erros), preferindo interfaces, tipos e funções de fábrica (factories).
1. Abstração e Modelagem de Dados
Em vez de classes, usamos interface ou type para definir a estrutura dos dados e funções puras para operar sobre eles. Isso mantém os dados "leves" e fáceis de serializar/compartilhar entre estados.
Exemplo: Entidade de Domínio (POO Funcional)
// ✅ Como fazer: Interface + Factory + Funções de Domínio
export interface Patient {
readonly id: string;
readonly name: string;
readonly birthDate: string; // ISO string
}
// Factory: Cria a "instância" (objeto literal)
export function createPatient(data: Partial<Patient>): Patient {
return {
id: data.id ?? crypto.randomUUID(),
name: data.name ?? 'Sem nome',
birthDate: data.birthDate ?? new Date().toISOString(),
};
}
// Comportamento: Funções que recebem o dado (Abstração)
export function calculatePatientAge(patient: Patient): number {
const birthDate = new Date(patient.birthDate);
const today = new Date();
let age = today.getFullYear() - birthDate.getFullYear();
const monthDiff = today.getMonth() - birthDate.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--;
}
return age;
}
export function isPatientOfLegalAge(patient: Patient): boolean {
const legalAge = 18;
return calculatePatientAge(patient) >= legalAge;
}2. Pilares da POO sem Classes
A. Encapsulamento (Closures e Modules)
O encapsulamento garante que dados sensíveis não sejam alterados de fora. Em TS/Web, usamos Closures ou apenas a exportação seletiva de arquivos (Modules).
// ✅ Como fazer: Usando Closures para estado privado
export function createAuthService() {
let _token: string | null = null; // "Privado" via Closure
function setToken(token: string) {
if (!token) throw new Error("Token inválido");
_token = token;
localStorage.setItem('auth_token', token);
}
function hasValidToken() {
return Boolean(_token);
}
return {
setToken,
hasValidToken
};
}
// Uso
const authService = createAuthService();
authService.setToken('abc-123');B. Herança vs Composição
Em POO tradicional, usa-se extends. No React/TS moderno, preferimos Composição de Tipos e Slots.
// Composição de Tipos
type AppErrorBase = {
message: string;
code: number;
};
type ApiError = AppErrorBase & {
endpoint: string;
};C. Herança de Erros (A Exceção)
As Classes de Erro são a única exceção onde incentivamos o uso de classes, pois permite o uso do operador instanceof para captura tipada de erros.
// ✅ Como fazer: Única exceção para uso de classes
export class AppError extends Error {
constructor(public message: string, public code: number) {
super(message);
this.name = "AppError";
}
}
export class UnauthorizedError extends AppError {
constructor() {
super("Sessão expirada.", 401);
this.name = "UnauthorizedError";
}
}D. Polimorfismo (Interfaces)
O polimorfismo no TS é atingido através da implementação de interfaces por diferentes objetos literais.
interface StorageProvider {
save(key: string, value: string): void;
}
export function createBrowserStorage(): StorageProvider {
return {
save(key: string, value: string) {
localStorage.setItem(key, value);
}
};
}
export function createMemoryStorage(): StorageProvider {
return {
save(key: string, value: string) {
console.log('Saving to memory:', key, value);
}
};
}3. Onde usar cada abordagem
No nosso projeto, seguimos estas diretrizes:
- UI (Telas e Componentes): Sempre Funcional.
- Entidades e Models: Interfaces + Objetos Literais (evite classes).
- Serviços e Infra: Objetos Literais ou Factories (evite classes).
- Tratamento de Erros: Classes (para usar
instanceof).
❌ O que NÃO usar classes
- API Models: Não crie classes que dão
newno JSON da API. Use interfaces e mapeadores (mappers). - Componentes React: Proibido
class extends React.Component. - Utils: Se é uma função, exporte a função diretamente.
Por que evitar classes no TS Web?
- Imutabilidade: Objetos literais funcionam melhor com o ecossistema de estado do React (Redux, TanStack) que exige imutabilidade. Classes muitas vezes incentivam mutação interna.
- Serialização: Classes perdem seus métodos quando enviadas via
postMessage(WebWorkers) ou salvas no Redux/LocalStorage. - Tamanho do Bundle: Funções e objetos literais sofrem melhor Tree Shaking do que classes.
- Sintaxe: O código fica mais conciso e alinhado ao padrão de Hooks do React.