Strictmode: entendendo cada flag do strict

1. O que é o strict mode e por que ativá-lo?

O strict mode no TypeScript é uma configuração que ativa um conjunto de verificações rigorosas de tipo, definido pela propriedade strict: true no tsconfig.json. Quando ativado, ele habilita automaticamente todas as flags individuais de strict, proporcionando a máxima segurança de tipos possível.

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

A diferença crucial entre ativar strict: true completo versus flags individuais é que o modo completo garante que nenhuma verificação crítica seja esquecida. Os benefícios incluem captura de erros em tempo de compilação, segurança de tipos aprimorada e código mais previsível.

2. strictNullChecks – O fim dos null/undefined silenciosos

Esta flag impede que null e undefined sejam atribuíveis a qualquer tipo, eliminando uma das fontes mais comuns de bugs em JavaScript.

// Sem strictNullChecks
let nome: string = null; // Permitido (perigoso!)

// Com strictNullChecks
let nome: string = null; // Erro: Type 'null' is not assignable to type 'string'

O impacto é significativo em parâmetros opcionais e retornos de função:

function encontrarUsuario(id: number): string | undefined {
  // Lógica de busca
  if (id === 0) return undefined;
  return "João";
}

const usuario = encontrarUsuario(0);
console.log(usuario.toUpperCase()); // Erro: Object is possibly 'undefined'

Estratégias de mitigação incluem narrowing, operador ! e optional chaining:

// Narrowing
if (usuario !== undefined) {
  console.log(usuario.toUpperCase()); // OK
}

// Non-null assertion (use com cuidado!)
console.log(usuario!.toUpperCase());

// Optional chaining
console.log(usuario?.toUpperCase());

3. noImplicitAny – Exigindo tipos explícitos

TypeScript infere any implicitamente quando não consegue determinar o tipo, o que pode levar a erros silenciosos em tempo de execução.

// Sem noImplicitAny
function processar(dado) {
  return dado.length; // any implícito - sem verificação
}

// Com noImplicitAny
function processar(dado: any) {
  return dado.length; // OK - explícito
}

function processar(dado: string) {
  return dado.length; // Melhor - tipado corretamente
}

Casos comuns incluem parâmetros de função não tipados e callbacks:

// Exemplo com callback
function executar(fn) { // Erro: Parameter 'fn' implicitly has 'any' type
  fn(42);
}

// Correção
function executar(fn: (x: number) => void) {
  fn(42);
}

noImplicitReturns complementa essa flag garantindo que funções com retorno explícito sempre retornem um valor:

function obterValor(flag: boolean): string {
  if (flag) {
    return "ativo";
  }
  // Erro: Not all code paths return a value
}

4. strictFunctionTypes – Segurança na covariância/contravariância

Esta flag corrige um comportamento perigoso onde parâmetros de função são tratados como bivariantes (permitindo tanto covariância quanto contravariância).

// Sem strictFunctionTypes (bivariante)
type Animal = { nome: string };
type Cachorro = Animal & { latir(): void };

function processarAnimais(animais: Animal[]) {}
function processarCachorros(cachorros: Cachorro[]) {}

let animais: Animal[] = [];
let cachorros: Cachorro[] = [];

animais = cachorros; // Permitido (covariante)
cachorros = animais; // Permitido (perigoso!)

// Com strictFunctionTypes (contravariante)
type Processador<T> = (item: T) => void;

let processarAnimal: Processador<Animal> = (a) => {};
let processarCachorro: Processador<Cachorro> = (c) => {};

processarAnimal = processarCachorro; // Erro! (correto)
processarCachorro = processarAnimal; // Permitido (seguro)

5. strictBindCallApply – Tipando métodos de contexto dinâmico

Esta flag adiciona assinaturas de tipo mais rigorosas para bind, call e apply, prevenindo erros comuns com o contexto this.

// Sem strictBindCallApply
function saudacao(this: { nome: string }) {
  return `Olá, ${this.nome}!`;
}

const contexto = { nome: "Maria" };
const saudacaoBind = saudacao.bind(contexto); // any

// Com strictBindCallApply
const saudacaoBind = saudacao.bind(contexto); // () => string - tipado corretamente!

// Exemplo com classe
class Contador {
  private valor = 0;

  incrementar() {
    this.valor++;
  }

  criarCallback() {
    // Sem strictBindCallApply, isso poderia causar erros
    return this.incrementar.bind(this);
  }
}

6. noUnusedLocals e noUnusedParameters – Código limpo e sem ruído

Essas flags eliminam variáveis e parâmetros não utilizados, mantendo o código mais limpo e fácil de manter.

// Sem noUnusedLocals
function calcular() {
  const resultado = 42; // Aviso: 'resultado' is declared but its value never read
  const temp = 10;
  return temp * 2;
}

// Com noUnusedLocals
function calcular() {
  const temp = 10;
  return temp * 2;
}

// Parâmetros não utilizados
function log(mensagem: string, _nivel: number) {
  console.log(mensagem);
  // _nivel prefixado com _ indica uso intencionalmente ignorado
}

7. exactOptionalPropertyTypes e useUnknownInCatchVariables – Flags menos conhecidas

exactOptionalPropertyTypes

Esta flag impede que propriedades opcionais recebam undefined sem declaração explícita:

// Sem exactOptionalPropertyTypes
interface Config {
  timeout?: number;
}

const config: Config = { timeout: undefined }; // Permitido

// Com exactOptionalPropertyTypes
const config: Config = { timeout: undefined }; // Erro!
const config: Config = { timeout: 5000 }; // OK
const config: Config = {}; // OK

useUnknownInCatchVariables

Força variáveis de catch a serem unknown em vez de any, promovendo tratamento de erro mais seguro:

// Sem useUnknownInCatchVariables
try {
  JSON.parse("inválido");
} catch (erro) {
  console.log(erro.message); // any - sem verificação
}

// Com useUnknownInCatchVariables
try {
  JSON.parse("inválido");
} catch (erro: unknown) {
  if (erro instanceof Error) {
    console.log(erro.message); // OK - verificado
  }
  console.log(erro.message); // Erro: Object is of type 'unknown'
}

Referências