Erros comuns em TypeScript que você deve evitar
TypeScript não é só “tipar”: é fazer o compilador te ajudar a encontrar inconsistências cedo.
Abaixo estão os deslizes mais frequentes (e perigosos) que eu vejo em projetos reais — com alternativas melhores.
Tipagem que “ment” pro compilador
Narrowing e união discriminada
Segurança em tempo de execução
1) Usar any como “atalho”
O erro mais comum é abrir mão do sistema de tipos. Quando você usa any, você desliga
justamente a parte do TypeScript que previne erros de integração, mudanças de contrato e bugs silenciosos.
Regra prática
Não “proteja” tipo com any. Troque por tipos reais:
interfaces, uniões e tipos auxiliares. Se estiver difícil, é sinal de que o contrato precisa ficar claro.
O que evitar:
function parse(data: any)sem validação e sem contrato de retorno.catch (e) { return e }come: anye retorno inconsistente.- “Destravar” erros com casts no lugar de corrigir o modelo de dados.
Sugestão: prefira tipos parametrizados e tipos de domínio (DTOs) com campos e invariantes explícitas.
2) Confiar em casts (as) sem checagem
Outro erro frequente é usar as para “convencer” o compilador. Isso é perigoso porque o compilador
passa a aceitar valores que podem não existir em runtime (ex.: JSON vindo de API, eventos, storage).
Se é externo (rede, arquivo, localStorage, worker, user input), o tipo precisa ser provado.
Diagnóstico rápido
Se você vê x as Tipo em pontos onde os dados ainda não foram verificados,
é provável que você esteja só adiando o bug para o runtime.
O melhor caminho é:
- Checar em runtime com type guards quando o dado entra no sistema.
- Modelar com uniões (por exemplo, eventos discriminados) em vez de forçar casts.
- Usar casts apenas em cenários seguros (ex.: valores internos que já foram normalizados).
3) Falhar no narrowing: ignorar uniões e discriminantes
TypeScript brilha quando você descreve estados diferentes com uniões e faz o narrowing funcionar.
Quando você não usa isso, você acaba com código cheio de if frágil ou com propriedades “possivelmente inexistentes”.
O erro comum aqui é tratar tudo como um único formato, mesmo quando o domínio tem variações.
Use discriminated unions para que o compilador entenda o que existe em cada caso.
Por que isso importa?
Sem discriminante, você perde o benefício do compilador para acessar campos corretos.
Com discriminante, o TypeScript faz narrowing automaticamente dentro de cada ramo.
Checklist do que evitar:
- “Um objeto com vários campos opcionais” onde na prática existem estados mutuamente exclusivos.
- Negligenciar
ine checagens por campo discriminante. - Dependência excessiva de
?.easpara contornar ausência de tipo.
4) Ignorar validação e “assumir” invariantes
Tipos são verificados em tempo de compilação. Mas seu código roda em runtime — e dados reais sempre chegam
com variações. Se você depende de invariantes (ex.: “campo X sempre existe”), valide na entrada.
Um erro comum é modelar funções como se elas fossem recebendo valores sempre válidos, mas na prática
elas recebem desde undefined até strings vazias e números fora do intervalo.
Estratégia simples
Confirme a forma do dado ao entrar (boundary) e mantenha o resto do código trabalhando com tipos
já “sanitizados”.
Impactos típicos de não validar:
- Falhas em cascata: um valor inválido vira
NaN, depois vira erro em renderização. - Estados impossíveis no domínio que passam a existir (ex.: status inválido).
- Tratamento reativo: corrigir erros um por um sem uma base consistente de tipos.
Código exemplo: type guard + união discriminada (sem casts perigosos)
Vamos supor que você recebe um evento e precisa processar corretamente conforme o type.
Abaixo, eu valido a entrada e uso um discriminante para o TypeScript fazer narrowing com segurança.
type UserEvent =
| { type: 'user:created'; userId: string; email: string }
| { type: 'user:updatedEmail'; userId: string; email: string }
| { type: 'user:deleted'; userId: string };
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;
}
function isUserEvent(value: unknown): value is UserEvent {
if (!isRecord(value)) return false;
const type = value.type;
const userId = value.userId;
const email = value.email;
if (typeof type !== 'string' || typeof userId !== 'string') return false;
switch (type) {
case 'user:created':
case 'user:updatedEmail':
return typeof email === 'string';
case 'user:deleted':
return typeof email === 'undefined';
default:
return false;
}
}
function handleEvent(input: unknown) {
if (!isUserEvent(input)) {
throw new Error('Evento inválido');
}
switch (input.type) {
case 'user:created':
console.log(`Criado: ${input.userId} (${input.email})`);
break;
case 'user:updatedEmail':
console.log(`Email atualizado: ${input.userId} (${input.email})`);
break;
case 'user:deleted':
console.log(`Deletado: ${input.userId}`);
break;
}
}
O ganho aqui
Você evita any e evita as Tipo. O compilador entende exatamente quais campos existem
em cada caso — e você só processa eventos quando eles realmente batem com o contrato.
Quer mais conteúdo prático?
Se você curte TypeScript bem feito, continua por aqui. No yurideveloper.com.br eu organizo posts técnicos
que ajudam você a melhorar código, reduzir bugs e deixar o compilador trabalhar a seu favor.
Sou Apaixonado pela programação e estou trilhando o caminho de ter cada diz mais conhecimento e trazer toda minha experiência vinda do Design para a programação resultando em layouts incríveis e idéias inovadoras! Conecte-se Comigo!