TypeScript Seguro: como habilitar strictNullChecks e noImplicitAny
Ativar verificações mais rígidas no TypeScript é uma das formas mais diretas de reduzir erros em produção, aumentar a previsibilidade do código e melhorar a qualidade geral do software. Entre as opções mais impactantes estão strictNullChecks (controle explícito de null e undefined) e noImplicitAny (bloqueio de tipos any inferidos).
Este artigo explica o que muda na prática, por que isso se conecta ao conceito de TypeScript Seguro, e como habilitar essas opções com um plano gradual para evitar “quebrar” o projeto.
Por que isso importa para um TypeScript Seguro
Em aplicações reais, muitos incidentes não vêm de falhas criptográficas ou ataques sofisticados, mas de erros de lógica: valores inesperados, validações ausentes e suposições incorretas sobre dados externos (APIs, banco, filas, inputs do usuário). TypeScript não é um mecanismo de segurança por si só, mas um sistema de tipos mais rigoroso reduz categorias inteiras de bugs que podem virar incidentes (como falhas de autorização por estado inconsistente, exceptions em rotas críticas, ou bypass de validações por undefined).
Duas fontes comuns de risco técnico:
null/undefinednão tratados: causam exceptions em tempo de execução e caminhos lógicos não previstos.anyimplícito: desativa a checagem de tipos em partes do código, permitindo inconsistências passarem despercebidas.
É exatamente aqui que strictNullChecks e noImplicitAny atuam.
O que é strictNullChecks
Quando strictNullChecks está desativado, o TypeScript é permissivo: null e undefined são aceitos em muitos lugares, e o compilador permite acessar propriedades sem garantir que o valor existe.
Quando está ativado, null e undefined passam a ser tratados como tipos próprios. Se uma variável pode ser undefined, isso precisa estar explícito no tipo (por exemplo, string | undefined). Com isso, o compilador obriga você a:
- checar existência antes de usar (
if (x) ...) - usar optional chaining (
obj?.prop) - fornecer fallback (
??) - ou garantir com narrowing/guards
Exemplo prático
Sem strictNullChecks:
function getUserName(user: { name: string } | null) {
return user.name; // pode quebrar em runtime se user for null
}
Com strictNullChecks habilitado, isso vira erro de compilação. O ajuste típico:
function getUserName(user: { name: string } | null) {
return user ? user.name : "anônimo";
}
Ou:
function getUserName(user: { name: string } | null) {
return user?.name ?? "anônimo";
}
O que é noImplicitAny
Quando noImplicitAny está desativado, o TypeScript permite que variáveis/parâmetros retornem para any sem você declarar. Isso é particularmente comum em:
- callbacks sem tipo
- parâmetros de função não anotados
- objetos construídos dinamicamente
- integrações com bibliotecas sem tipagem adequada
Quando noImplicitAny está ativado, o compilador passa a exigir tipo explícito (ou inferência correta). Se não houver informação suficiente para inferir, vira erro.
Exemplo prático
Sem noImplicitAny:
function logValue(value) {
console.log(value);
}
Com noImplicitAny, value vira erro. Correções comuns:
function logValue(value: unknown) {
console.log(value);
}
Ou, se você sabe o tipo:
function logValue(value: string) {
console.log(value);
}
Para um TypeScript Seguro, unknown é frequentemente mais apropriado do que any em dados externos (como payloads de API), porque força validação/narrowing antes do uso.
Como habilitar no tsconfig.json
Em projetos TypeScript, essas opções ficam em compilerOptions. Você pode ativá-las diretamente:
{
"compilerOptions": {
"strictNullChecks": true,
"noImplicitAny": true
}
}
Observação importante: strict: true ativa um conjunto de verificações, incluindo strictNullChecks e noImplicitAny. Se você já usa strict: true, possivelmente elas já estão ligadas:
{
"compilerOptions": {
"strict": true
}
}
Se a meta é um endurecimento gradual, é comum manter strict: false e ativar as flags individualmente — ou vice-versa, dependendo do estágio do projeto.
Estratégia segura: habilitar sem paralisar o time
Ativar essas opções em um projeto com legado pode gerar dezenas ou centenas de erros. O objetivo não deve ser “corrigir tudo em um commit”, mas criar um plano que aumente a segurança do código continuamente.
Passo 1: medir o impacto antes de mudar nada
Rode o build/checagem de tipos e guarde a linha de base:
tsc --noEmit- ou o comando do seu bundler (Vite, Next.js, etc.) com checagem de tipos
Registre quantos erros existem hoje e onde.
Passo 2: habilitar uma flag por vez
Em muitos casos, noImplicitAny tende a gerar menos “efeito cascata” do que strictNullChecks, mas isso varia por base de código.
Uma ordem frequentemente viável:
noImplicitAny: truestrictNullChecks: true
Passo 3: tratar erros por “bordas” do sistema primeiro
Para um TypeScript Seguro, comece por pontos onde entram dados não confiáveis:
- controllers/handlers HTTP
- consumo de APIs de terceiros
- parsing de JSON
- eventos de fila/stream
- leitura de variáveis de ambiente
- acesso a banco (mapeamento de nullables)
Isso reduz risco real e evita que any/undefined se espalhem.
Corrigindo erros comuns de noImplicitAny
1) Parâmetros sem tipo
Antes:
function formatDate(date) {
return date.toISOString();
}
Depois:
function formatDate(date: Date) {
return date.toISOString();
}
2) Callbacks e funções de array
Antes:
items.map((item) => item.id);
Se items estiver mal tipado, item pode virar any. A correção normalmente é tipar a origem:
type Item = { id: string };
const items: Item[] = getItems();
const ids = items.map((item) => item.id);
3) Dados externos: prefira unknown e valide
Antes:
function handlePayload(payload: any) {
console.log(payload.user.name);
}
Depois (mínimo aceitável):
function handlePayload(payload: unknown) {
if (typeof payload === "object" && payload !== null && "user" in payload) {
// ainda pode precisar de validação mais detalhada
console.log((payload as any).user);
}
}
Em sistemas críticos, o ideal é validar schema (por exemplo, com bibliotecas de validação) antes de confiar na forma do objeto. O ponto aqui é: unknown impede o uso direto sem checagem.
Corrigindo erros comuns de strictNullChecks
1) Propriedades opcionais
Antes:
type User = { name?: string };
function greet(user: User) {
return "Olá, " + user.name.toUpperCase();
}
Depois:
type User = { name?: string };
function greet(user: User) {
return "Olá, " + (user.name?.toUpperCase() ?? "visitante");
}
2) Valores potencialmente undefined
Antes:
const token = process.env.API_TOKEN;
useToken(token);
Com strictNullChecks, token é string | undefined. Você precisa decidir: falhar cedo ou fornecer fallback.
Falhar cedo (mais seguro):
const token = process.env.API_TOKEN;
if (!token) throw new Error("API_TOKEN ausente");
useToken(token);
3) Acesso a resultado de busca
Antes:
const user = users.find((u) => u.id === id);
return user.name;
Depois:
const user = users.find((u) => u.id === id);
if (!user) throw new Error("Usuário não encontrado");
return user.name;
Ou, se ausência é aceitável:
const user = users.find((u) => u.id === id);
return user?.name ?? null;
Como reduzir impacto em bases grandes
Use tipagem incremental com tsconfig e projetos
Se seu repositório tem múltiplos pacotes (monorepo) ou módulos, você pode habilitar as flags em partes:
- ativar primeiro em bibliotecas internas reutilizadas
- depois em serviços com menos acoplamento
- por fim em camadas mais difíceis (UI antiga, código legado)
Evite “silenciar” com any por conveniência
Ao corrigir noImplicitAny, a tentação é declarar : any para encerrar o erro. Isso resolve o compilador, mas mantém o risco. Pergunta prática a fazer em cada caso:
- esse dado vem de fora? Use
unknowne valide/narrow. - é interno e conhecido? Tipar corretamente é o caminho.
- é um caso complexo e temporário? Documente e crie tarefa para remover, mas minimize a superfície.
Crie regras no lint para sustentar o ganho
Mesmo com o compilador mais rígido, lint ajuda a manter padrões. Em ambientes com ESLint, regras úteis incluem:
- evitar
anyexplícito (quando possível) - exigir checagens consistentes em condições
- padronizar retornos e tratamento de
null
O objetivo é impedir regressões enquanto a migração acontece.
Checklist de adoção (resumo operacional)
- Rodar
tsc --noEmite registrar o estado atual. - Ativar
noImplicitAny: truee corrigir por módulos:- tipar parâmetros e retornos
- tipar coleções e callbacks pela fonte
- usar
unknownem inputs externos
- Ativar
strictNullChecks: truee corrigir:- optional chaining (
?.) e nullish coalescing (??) - guards (
if (!x) throw ...) - tipos união (
T | undefined) quando apropriado
- optional chaining (
- Priorizar bordas do sistema e fluxos críticos.
- Estabilizar com lint e revisão de PR focada em tipos.
Conclusão
Habilitar strictNullChecks e noImplicitAny é um passo concreto rumo a um TypeScript Seguro: você reduz a chance de exceções por valores ausentes, evita que any desative a checagem de tipos silenciosamente e força decisões explícitas sobre ausência de dados. Em projetos maduros, a chave é uma migração gradual, guiada por risco, começando pelos pontos onde dados externos entram e onde falhas têm maior impacto.
Se você quiser, posso propor um roteiro baseado no seu tsconfig.json atual e em um exemplo de erro típico que apareceu após ativar essas flags (basta colar aqui um trecho do tsconfig e 2–3 mensagens do compilador).