Todo projeto TypeScript que eu audito tem "strict": true no tsconfig.json. O mesmo projeto tem // @ts-ignore espalhado pelo código, any em pontos críticos e ninguém no time que consiga explicar o que a flag protege.
"strict": true ativa um grupo de flags do compilador. Cada flag existe porque uma classe de bug chegou em produção antes de alguém escrever o teste que a pegaria. O mapa de flags troca o reclame do compilador por um inventário de riscos do seu código.
O que "strict": true habilita
strictNullChecks
Sem ela, null e undefined entram em qualquer tipo. Com ela, o compilador cobra o tratamento da ausência no ponto em que o valor pode faltar.
// sem strictNullChecks: compila, quebra em runtime
function getUser(id: string): User {
return db.find(id) // pode devolver undefined
}
const nome = getUser('123').name // TypeError: Cannot read property 'name' of undefined
// com strictNullChecks: o compilador aponta a linha
function getUser(id: string): User | undefined {
return db.find(id)
}
const user = getUser('123')
const nome = user?.name
A referência da flag descreve a regra de atribuição. Os TypeError que sobrevivem em produção e que eu consigo rastrear até a origem quase sempre são leitura de propriedade em valor ausente.
noImplicitAny
Sem ela, parâmetro de função sem tipo vira any sem aviso e a checagem desliga para a função inteira. Com ela, o compilador exige o tipo declarado.
// sem noImplicitAny: param é any implícito
function process(data) {
return data.value.toUpperCase() // nada é verificado
}
// com noImplicitAny: o compilador pede o tipo
function process(data: { value: string }) {
return data.value.toUpperCase()
}
A documentação da flag é curta. Em migração, ela é a que gera erro em massa: qualquer arquivo sem tipos declarados quebra de uma vez.
strictFunctionTypes
Compara tipos de função pela variação correta dos parâmetros. Sem a flag, o TypeScript aceita qualquer direção; com ela, um handler que espera MouseEvent não sobe para um slot que recebe Event, porque o caller pode passar um Event puro e o handler quebra.
type Handler = (event: Event) => void
const myHandler: Handler = (event: MouseEvent) => {
// strictFunctionTypes aponta o erro
}
Declaração de método (foo() {}) continua bivariante por regra da especificação. Escreva callback como propriedade de função quando quiser a checagem. A referência da flag explica a exceção dos métodos.
strictPropertyInitialization
Cobra inicialização de propriedade de classe no constructor. Evita leitura de campo que o compilador aceitava como pronto e que chega como undefined em runtime.
class UserService {
private db: Database // erro: não recebe valor no constructor
constructor() {}
}
// correto: dependência injetada no constructor
class UserService {
private db: Database
constructor(db: Database) {
this.db = db
}
}
// injeção fora do constructor, com definite assignment assertion
class UserService {
private db!: Database
}
O operador ! registra a intenção e desliga a checagem daquele campo. A documentação da flag cobre o operador.
Duas flags fora do strict que valem o custo
noUncheckedIndexedAccess
Acesso por índice devolve T | undefined em vez de T. Cobre o bug de assumir que users[0] existe, comum quando a lista vem de filtro de API.
// tsconfig.json
{ "compilerOptions": { "noUncheckedIndexedAccess": true } }
const items = ['a', 'b', 'c']
const first = items[0] // string | undefined
const safe = first?.toUpperCase()
A flag gera ruído em for sobre array e em mapa com chave dinâmica. O time absorve esse custo uma vez e para de receber undefined em runtime. Documentação da flag.
exactOptionalPropertyTypes
Separa { prop?: string } (o campo pode faltar) de { prop: string | undefined } (o campo chega, com valor undefined). Sem a flag, o TypeScript trata as duas formas como iguais e você escreve guarda de null em campo que a API envia presente. Documentação da flag.
Migrar projeto legado para strict
Ligar "strict": true de uma vez em monorepo de 200 mil linhas trava o time por semanas. O caminho que eu uso:
- Ligue
"strict": truee cubra os erros com// @ts-ignore. Aceite o débito como temporário e visível. - Instale
@typescript-eslint/ban-ts-commente configure para aceitar@ts-expect-errore rejeitar@ts-ignore. O legado continua no repositório; código novo que silencia precisa justificar com o comentário de erro esperado, que o compilador valida.@ts-ignorenão valida nada. - Resolva os silenciadores arquivo por arquivo, começando pelos módulos com mais chamadas em produção.
O código novo recebe strict no dia um. O legado entra conforme cada arquivo é corrigido, e a contagem de @ts-expect-error no repositório vira o número de progresso que o time acompanha.
O custo de silenciar o compilador
Cada // @ts-ignore, cada as any, cada flag desligada é uma aposta: você confia no seu raciocínio mais do que na checagem daquele ponto. Em biblioteca externa com tipos ruins, a aposta faz sentido. Em código interno, o bug que a flag pegava volta em runtime, e o relatório de erro chega semanas depois, longe da linha que causou o problema.
Quando o compilador aponta uma linha e você não entende o motivo, o caminho é ler o erro e corrigir o tipo. O capítulo de narrowing do handbook cobre as situações de undefined e de tipo condicional que aparecem com mais frequência.
Referências
- strict, strictNullChecks, strictFunctionTypes, strictPropertyInitialization (referência do tsconfig, TypeScript)
- noUncheckedIndexedAccess, exactOptionalPropertyTypes (referência do tsconfig, TypeScript)
- Narrowing (TypeScript Handbook)
- ban-ts-comment (typescript-eslint)
Curtiu o conteúdo?
Construo produtos web e soluções com IA do jeito certo — arquitetura sólida, código sustentável e entrega real.
Vamos conversar