A maioria dos projetos TypeScript que analiso tem "strict": true no tsconfig.json — mas também tem // @ts-ignore espalhados pelo código, any em lugares estratégicos, e engenheiros que não sabem exatamente o que o strict mode está protegendo.

Strict mode não é um conjunto arbitrário de restrições para fazer o compilador chato. É uma coleção de flags, cada uma capturando uma categoria específica de bug que acontece em runtime. Entender o que cada flag faz transforma "o TypeScript me obrigou a fazer isso" em "o TypeScript me impediu de cometer esse erro".

O que "strict": true habilita

"strict": true é um atalho para habilitar um conjunto de flags. Vamos pelo que realmente importa:

strictNullChecks

A mais importante. Sem ela, null e undefined são assignáveis a qualquer tipo. Com ela, você precisa lidar explicitamente com a possibilidade de ausência.

// Sem strictNullChecks — compila, pode explodir em runtime
function getUser(id: string): User {
  return db.find(id) // pode retornar undefined
}
const name = getUser('123').name // TypeError: Cannot read property 'name' of undefined

// Com strictNullChecks — erro em tempo de compilação
function getUser(id: string): User | undefined {
  return db.find(id)
}
const user = getUser('123')
const name = user?.name // forçado a lidar com o undefined

noImplicitAny

Força a tipagem explícita quando o TypeScript não consegue inferir o tipo. Sem isso, parâmetros de função sem tipo viram any silenciosamente — e você perde toda a proteção do sistema de tipos.

// ❌ Sem noImplicitAny — param é `any` implicitamente
function process(data) { // param: any
  return data.value.toUpperCase() // sem verificação
}

// ✅ Com noImplicitAny — você declara a intenção
function process(data: { value: string }) {
  return data.value.toUpperCase()
}

strictFunctionTypes

Garante verificação correta de tipos em callbacks. Sem ela, funções são verificadas bivariantly — o que pode deixar bugs de tipo passar.

// strictFunctionTypes captura este bug
type Handler = (event: MouseEvent) => void
const myHandler: Handler = (event: Event) => {} // ❌ Erro correto — MouseEvent é mais específico

strictPropertyInitialization

Garante que propriedades de classe sejam inicializadas no constructor. Previne leituras de propriedades undefined que parecem definidas.

class UserService {
  private db: Database // ❌ Erro: não inicializado no constructor

  constructor() {
    // esqueceu de inicializar db
  }
}

// ✅ Correto
class UserService {
  private db: Database

  constructor(db: Database) {
    this.db = db
  }
}

Flags que não estão no strict mas que você deveria habilitar

noUncheckedIndexedAccess

Acessos a arrays e objetos com index retornam T | undefined ao invés de T. Previne um dos bugs mais comuns: assumir que um index sempre existe.

// tsconfig.json
{ "compilerOptions": { "noUncheckedIndexedAccess": true } }

const items = ['a', 'b', 'c']
const first = items[0] // tipo: string | undefined (não string)
const safe = first?.toUpperCase() // forçado a verificar

exactOptionalPropertyTypes

Diferencia { prop?: string } (prop pode estar ausente) de { prop: string | undefined } (prop presente mas undefined). São coisas diferentes, e sem essa flag, TypeScript trata igual.

Como migrar um projeto legado para strict

Habilitar strict de uma vez num projeto grande não é prático. A estratégia que funciona:

  1. Habilite "strict": true e use // @ts-ignore em tudo que quebrar (sim, temporariamente)
  2. Crie uma regra de lint que proíbe novos // @ts-ignore — o legado pode existir, o novo não
  3. Gradualmente, resolva os @ts-ignore arquivo por arquivo, priorizando os mais críticos

Isso dá o benefício imediato de strict no código novo, enquanto o legado é corrigido de forma incremental.

Por que não desabilitar o que você não entende

Cada vez que você adiciona // @ts-ignore, as any, ou desabilita uma flag, você está dizendo: "Confio em mim mesmo mais do que no compilador aqui." Às vezes isso é legítimo — ao trabalhar com bibliotecas mal tipadas, por exemplo.

Na maioria das vezes, é fuga. E o TypeScript está te protegendo de algo que vai explodir em runtime — talvez não amanhã, talvez em 6 meses, talvez em produção numa sexta às 23h.

Quando você não entende por que o TypeScript está reclamando, a resposta certa é entender, não silenciar. Isso é a diferença entre usar TypeScript e usar TypeScript bem.