TypeScript como mental model: tipos são prova, não anotação
Ao terminar: Você lê um tipo TypeScript como uma prova sobre o runtime, não como comentário decorativo.
O clique mental: tipos são asserções provadas
| Sistema nominal (Java, C#) | Sistema estrutural (TypeScript) | Consequência prática |
|---|---|---|
| O tipo é a declaração | O tipo é a FORMA | Objeto literal com os campos certos satisfaz a interface sem implementá-la |
| Compatibilidade exige herança | Compatibilidade exige forma compatível | Dá para tipar biblioteca de terceiros sem tocar nela |
| Duas classes idênticas são incompatíveis | Duas formas idênticas são o mesmo tipo | Precisa de campo discriminante para diferenciar de propósito |
| O tipo existe em execução | O tipo some na transpilação | Nenhuma verificação sobrevive ao build — validação de borda é obrigatória |
A maioria dos devs aprende TS como "JavaScript com anotações". Essa visão funciona em tutorial de 5 minutos, mas colapsa ao escrever código sério. O mental model correto é outro:
Um tipo é uma asserção sobre os valores possíveis. O compilador percorre o control flow e prova que cada uso do valor respeita essa asserção. Quando a prova falha, erro de compilação.
Isso é diferente de "anotação" porque assertions têm consequências lógicas. Se x: string | null, você não pode chamar x.toUpperCase() sem antes eliminar o null. O tipo força você a raciocinar sobre o caso que quebra.
Estrutural, não nominal
Em Java/C#, tipos são nominais: class Dog e class Wolf são incompatíveis mesmo com a mesma API. TS é o oposto — compatibilidade por shape.
interface Point2D { x: number; y: number; }
interface Position { x: number; y: number; z: number; }
function plot(p: Point2D) { /* ... */ }
const pos: Position = { x: 1, y: 2, z: 3 };
plot(pos); // ✅ funciona — pos tem pelo menos {x, y}Isso dá flexibilidade brutal (duck typing tipado) mas exige disciplina: às vezes dois tipos acidentalmente têm o mesmo shape e colidem. Solução clássica: branded types.
type UserId = string & { readonly __brand: 'UserId' };
type PostId = string & { readonly __brand: 'PostId' };
declare function getUser(id: UserId): User;
const postId = 'abc' as PostId;
getUser(postId); // ❌ erro — shapes não batem pelo brandInference é a maior feature — use-a
Devs que vêm de Java/C# escrevem tipos em tudo. Isso é antipattern em TS. A regra prática:
Anote boundaries (funções públicas, argumentos, retornos de API). Deixe o resto ser inferido. TS infere melhor que você em 90% dos casos internos.
// Ruim — anota o óbvio
const items: string[] = ['a', 'b', 'c'];
const total: number = items.length;
// Bom — boundary anotado, resto inferido
function parseUsers(raw: unknown): User[] {
// ...
}
const users = parseUsers(data); // User[] inferidoQual é o modelo mental correto do sistema de tipos dessa linguagem?
Por que isso importa na prática
Com o mental model certo, você para de "lutar contra o TS" e começa a escolher invariantes bem. Quando projeta uma função, a primeira pergunta não é mais "que código escrever" e sim "quais tipos representam os estados válidos — de forma que os inválidos nem consigam ser expressos?". Este é o salto de júnior pra sênior em TS. Os próximos módulos da trilha operacionalizam esse princípio.
Perguntas frequentes
❓ TypeScript é só JavaScript com comentários de tipo?
❓ O tipo protege em tempo de execução?
❓ Vale usar o modo mais estrito?
Fixando
O que significa dizer que o sistema de tipos é estrutural?
Qual prática reduz mais a chance de erro em fronteira de sistema?
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…