Erros comuns e como evitá-los
1. Erros com any implícito e explícito
O any é o maior vilão do TypeScript. Ele desativa completamente a verificação de tipos, transformando seu código em JavaScript sem segurança.
// ❌ Ruim: any implícito
function processData(data) {
return data.name; // Erro: 'data' é implicitamente 'any'
}
// ✅ Correto: ative noImplicitAny no tsconfig.json
// "noImplicitAny": true (ou "strict": true)
// ❌ Ruim: as any explícito
const user = JSON.parse('{"name": "João"}') as any;
console.log(user.email); // Sem erro de compilação, mas runtime crash
// ✅ Correto: use unknown com type guards
const userData: unknown = JSON.parse('{"name": "João"}');
if (typeof userData === 'object' && userData !== null && 'name' in userData) {
const user = userData as { name: string };
console.log(user.name);
}
// ✅ Alternativa: use zod ou io-ts para validação runtime
import { z } from 'zod';
const UserSchema = z.object({ name: z.string() });
const parsedUser = UserSchema.parse(JSON.parse('{"name": "João"}'));
2. Problemas com tipos null e undefined
O erro "Object is possibly 'null'" é um dos mais comuns em TypeScript.
// ❌ Ruim: sem strictNullChecks
function getLength(str: string | null) {
return str.length; // Pode explodir se str for null
}
// ✅ Correto: ative strictNullChecks
function getLength(str: string | null) {
// Opção 1: early return
if (str === null) return 0;
// Opção 2: optional chaining
return str?.length ?? 0;
// Opção 3: nullish coalescing
return (str ?? '').length;
}
// ❌ Ruim: uso excessivo de non-null assertion (!)
const element = document.getElementById('root')!;
element.innerHTML = 'Hello'; // Perigoso se o elemento não existir
// ✅ Correto: verificação adequada
const element = document.getElementById('root');
if (element) {
element.innerHTML = 'Hello';
}
3. Erros de tipo em arrays e objetos
Mutabilidade inesperada e tipagem inadequada são fontes comuns de bugs.
// ❌ Ruim: array mutável exposto
class UserRepository {
private users: Array<{ id: number; name: string }> = [];
getUsers() {
return this.users; // Permite mutação externa
}
}
// ✅ Correto: ReadonlyArray
class UserRepository {
private users: Array<{ id: number; name: string }> = [];
getUsers(): ReadonlyArray<{ id: number; name: string }> {
return [...this.users]; // Cópia defensiva
}
}
// ❌ Ruim: Object.keys com tipagem inadequada
const user = { name: 'Ana', age: 30 } as const;
Object.keys(user).forEach(key => {
console.log(user[key]); // Erro: key é string, não 'name' | 'age'
});
// ✅ Correto: type assertion segura
(Object.keys(user) as Array<keyof typeof user>).forEach(key => {
console.log(user[key]); // Funciona
});
// ❌ Ruim: tupla sem as const
const position = [10, 20]; // type: number[]
const x = position[0]; // number, não 10
// ✅ Correto: as const para tuplas literais
const position = [10, 20] as const; // type: readonly [10, 20]
4. Problemas com funções e overloads
Overloads mal definidos e perda de contexto com this são armadilhas frequentes.
// ❌ Ruim: overloads em ordem incorreta
function format(value: string): string;
function format(value: number): string;
function format(value: string | number): string {
return String(value);
}
// ✅ Correto: overloads do mais específico para o mais genérico
function format(value: string): string;
function format(value: number): string;
function format(value: string | number): string {
return String(value);
}
// ❌ Ruim: perda de contexto com this
class Button {
text = 'Click me';
onClick() {
console.log(this.text); // this pode ser undefined
}
}
const button = new Button();
setTimeout(button.onClick, 1000); // Erro: this é undefined
// ✅ Correto: arrow function ou bind
class Button {
text = 'Click me';
onClick = () => { // Arrow function preserva this
console.log(this.text);
}
}
// ✅ Alternativa: bind
setTimeout(button.onClick.bind(button), 1000);
// ❌ Ruim: Function como tipo
function executeCallback(cb: Function) {
cb(1, 2); // Sem segurança de tipos
}
// ✅ Correto: interface de callback
interface Callback {
(a: number, b: number): number;
}
function executeCallback(cb: Callback) {
return cb(1, 2);
}
5. Erros com genéricos e inferência
Genéricos mal dimensionados podem causar inferência falha ou tipos muito restritivos.
// ❌ Ruim: genérico muito amplo
function identity<T>(value: T): T {
return value;
}
// ✅ Correto: genérico restrito quando necessário
function processArray<T extends { id: number }>(items: T[]): number[] {
return items.map(item => item.id);
}
// ❌ Ruim: inferência falhando com tipos condicionais
type IsString<T> = T extends string ? true : false;
type Result = IsString<number>; // false
// ✅ Correto: use infer com cuidado
type ElementType<T> = T extends (infer U)[] ? U : never;
type Num = ElementType<number[]>; // number
// ❌ Ruim: const genérico sem necessidade
function createPair<T extends string>(a: T, b: T) {
return [a, b] as const;
}
// ✅ Correto: inferência automática funciona
function createPair<T>(a: T, b: T) {
return [a, b] as const;
}
6. Problemas com módulos e importação de tipos
Importações circulares e escolha errada entre import type e import causam problemas.
// ❌ Ruim: importação circular
// user.ts
import { Post } from './post';
export class User {
posts: Post[];
}
// post.ts
import { User } from './user';
export class Post {
author: User;
}
// ✅ Correto: interface compartilhada
// types.ts
export interface IUser { posts: IPost[]; }
export interface IPost { author: IUser; }
// ❌ Ruim: import type vs import
import { User } from './user'; // Inclui código desnecessário
// ✅ Correto: use import type para evitar tree-shaking
import type { User } from './user'; // Apenas tipo
// ❌ Ruim: export default em tipos
export default interface User {
name: string;
}
// ✅ Correto: export named
export interface User {
name: string;
}
7. Erros com classes e herança
Confundir implements com extends e erros de covariância/contravariância são comuns.
// ❌ Ruim: implements vs extends
interface Animal {
makeSound(): void;
}
class Dog implements Animal {
makeSound() {
console.log('Woof');
}
}
class Puppy extends Dog { // Herda implementação
makeSound() {
console.log('Yip');
}
}
// ❌ Ruim: covariância incorreta em métodos
class Parent {
getValue(): number | string {
return 42;
}
}
class Child extends Parent {
getValue(): number { // Retorno mais específico - OK
return 42;
}
}
// ❌ Ruim: uso incorreto de private vs #
class OldWay {
private secret = 'hidden'; // Ainda acessível via eval()
}
class ModernWay {
#secret = 'hidden'; // Verdadeiramente privado
}
8. Erros com tipos avançados (utility types e mapped types)
Utility types mal aplicados podem causar erros sutis.
// ❌ Ruim: Partial aplicado incorretamente
interface Config {
url: string;
timeout: number;
}
function createConfig(config: Partial<Config>) {
return {
url: config.url ?? 'default', // Pode ser undefined
timeout: config.timeout ?? 5000,
};
}
// ✅ Correto: Required para campos obrigatórios
function createConfig(config: Required<Config>) {
return config;
}
// ❌ Ruim: Pick com chaves que não existem
type UserKeys = Pick<User, 'name' | 'email'>; // Erro se 'email' não existir
// ✅ Correto: verificação com keyof
type UserKeys = Pick<User, keyof User>;
// ❌ Ruim: Record com chaves literais problemáticas
type StatusMap = Record<'active' | 'inactive', boolean>;
const statuses: StatusMap = {
active: true,
inactive: false,
};
Conclusão
Evitar esses erros comuns requer prática e atenção aos detalhes. Use sempre strict: true no tsconfig.json, prefira unknown a any, e mantenha seus tipos o mais específicos possível sem serem restritivos demais. Lembre-se: TypeScript é uma ferramenta para ajudar, não para atrapalhar. Use seus recursos com sabedoria.
Referências
- TypeScript Handbook: Everyday Types — Documentação oficial cobrindo tipos básicos e práticas recomendadas
- TypeScript Deep Dive: TypeScript Errors — Guia abrangente sobre erros comuns e como resolvê-los
- TypeScript: Strict Mode — Documentação oficial sobre o modo strict e suas implicações
- Zod Documentation — Biblioteca de validação runtime que complementa TypeScript
- TypeScript Playground — Ferramenta oficial para experimentar código TypeScript e testar tipos
- Clean Code TypeScript — Guia de boas práticas e padrões para TypeScript limpo