Como usar o modo strict do TypeScript sem sofrer na migração
1. Entendendo o modo strict e seus benefícios
O modo strict do TypeScript não é uma única configuração, mas um conjunto de flags de verificação rigorosa que, quando ativadas, transformam o compilador em um guardião implacável de segurança de tipos. As principais flags incluem noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization e noImplicitThis. Ao ativar strict: true no tsconfig.json, todas essas flags são habilitadas simultaneamente.
Os benefícios são substanciais: prevenção de bugs comuns como acessar propriedades de objetos nulos, redução de erros em produção que poderiam ser capturados em tempo de compilação, e documentação viva do código através de tipos explícitos. A diferença entre strict total e strict parcial reside na possibilidade de ativar flags individualmente, permitindo uma adoção gradual.
// tsconfig.json - strict total
{
"compilerOptions": {
"strict": true
}
}
// tsconfig.json - strict parcial (flags individuais)
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}
2. Preparação do ambiente e configuração inicial
Antes de ativar o modo strict, é fundamental realizar um levantamento do código legado. Mapeie todos os arquivos e dependências críticas, identificando módulos que mais utilizam any implícito ou manipulam valores nulos sem verificação. Crie uma planilha com a contagem de erros por arquivo após ativar strict: true.
A configuração do tsconfig.json deve começar com strict: true, mas com ajustes finos. Ative strictNullChecks e noImplicitAny como ponto de partida, deixando flags mais agressivas para fases posteriores. Ferramentas auxiliares como ESLint com regras TypeScript, Husky para pré-commit e pipelines de CI/CD com verificação incremental são essenciais para manter a disciplina durante a migração.
// tsconfig.json - configuração inicial para migração
{
"compilerOptions": {
"strict": true,
"strictNullChecks": true,
"noImplicitAny": true,
"strictPropertyInitialization": false,
"noUnusedLocals": "warning"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
3. Estratégia de migração gradual por módulos
A abordagem mais eficaz é dividir o projeto em camadas e migrar módulos de baixo acoplamento primeiro. Utilitários, helpers e funções puras são ideais para começar, pois possuem menos dependências e impacto reduzido. Depois, avance para módulos core, serviços e componentes de UI.
Use // @ts-strict em arquivos específicos ou configure arquivos com strict: false no tsconfig.json para transição controlada. Crie um backlog de issues para cada módulo, priorizando por risco de bugs. Módulos que lidam com dados externos (APIs, bancos de dados) devem ser prioridade máxima.
// tsconfig.json - migração por módulos
{
"compilerOptions": {
"strict": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "src/legacy/**/*"]
}
// src/legacy/module-old.ts - arquivo legado com strict desativado
// @ts-strict
// Este arquivo será migrado na sprint 3
export function processData(data: any) {
return data.value;
}
4. Lidando com strictNullChecks sem dor de cabeça
O strictNullChecks é frequentemente a flag que mais causa impacto. Identifique valores nulos e indefinidos usando operadores modernos: optional chaining (?.) para acesso seguro a propriedades e nullish coalescing (??) para valores padrão. Refatore funções que retornam null para usar tipos união (T | null) ou crie Option Types.
Para bibliotecas externas sem tipos, crie declarações de tipos customizadas usando declare module e type guards. Evite o uso de ! (non-null assertion) como solução paliativa — ele deve ser reservado para casos onde você tem certeza absoluta que o valor não é nulo.
// Antes - sem strictNullChecks
function getUserName(user: User) {
return user.name.toUpperCase(); // Pode explodir se user for null
}
// Depois - com strictNullChecks
function getUserName(user: User | null): string {
return user?.name?.toUpperCase() ?? 'Usuário não encontrado';
}
// Type guard para bibliotecas externas
function isValidUser(obj: unknown): obj is User {
return (
typeof obj === 'object' &&
obj !== null &&
'name' in obj &&
typeof (obj as User).name === 'string'
);
}
5. Resolvendo noImplicitAny e noUnusedLocals
O noImplicitAny força você a declarar tipos explicitamente. Substitua any implícito por tipos concretos. Para dados desconhecidos, use unknown em vez de any — isso força verificações antes do uso. Evite o uso excessivo de as (type assertion), que pode mascarar problemas reais.
Para parâmetros de callback e funções de alta ordem, utilize genéricos e interfaces. O noUnusedLocals pode ser configurado como warning temporário durante a migração, mas eventualmente deve se tornar erro. Use _ como prefixo para parâmetros descartados intencionalmente.
// Antes - noImplicitAny desativado
function processItems(items) {
return items.map(item => item.value);
}
// Depois - com noImplicitAny ativado
interface Item {
value: number;
}
function processItems(items: Item[]): number[] {
return items.map((item: Item): number => item.value);
}
// Uso de unknown para dados desconhecidos
function parseJson(data: string): unknown {
return JSON.parse(data);
}
// Parâmetro descartado intencionalmente
array.forEach((_element: unknown, index: number) => {
console.log(index);
});
6. Tratamento de erros comuns durante a migração
Problemas com this implícito são frequentes em código legado. Use arrow functions para preservar o contexto ou declare this explicitamente como parâmetro. Conflitos com bibliotecas JavaScript sem tipos podem ser resolvidos criando arquivos .d.ts locais e usando skipLibCheck para ignorar bibliotecas problemáticas.
Erros de tipo em testes unitários exigem ajuste de mocks e stubs. Use jest.Mock ou vi.fn() com tipos adequados. Evite as any em testes — isso derrota o propósito de ter tipos seguros.
// Problema com this implícito
class OldComponent {
name: string;
constructor() {
this.name = 'Component';
// this é any implícito aqui
setTimeout(function() {
console.log(this.name); // Erro!
}, 100);
}
}
// Solução com arrow function
class NewComponent {
name: string;
constructor() {
this.name = 'Component';
setTimeout(() => {
console.log(this.name); // Correto
}, 100);
}
}
// Mock tipado para testes
const mockService: jest.Mock<Promise<Data>> = jest.fn();
mockService.mockResolvedValue({ id: 1, value: 'test' });
7. Manutenção contínua e boas práticas pós-migração
Após a migração, implemente monitoramento contínuo usando tsc --noEmit em pipelines e dashboards de qualidade. Configure o Husky para executar verificação de tipos antes de cada commit. Invista em treinamento da equipe com code reviews focados em tipos e documentação de padrões internos.
A evolução não para quando strict: true está ativado. Considere habilitar flags adicionais como exactOptionalPropertyTypes e noUncheckedIndexedAccess conforme a maturidade do código aumenta. Essas flags oferecem segurança ainda maior para propriedades opcionais e acesso a índices de arrays.
// Pipeline CI/CD com verificação de tipos
// .github/workflows/ci.yml
name: TypeScript Check
on: [push]
jobs:
type-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm ci
- run: npx tsc --noEmit
- run: npx eslint src/
// Configuração avançada pós-migração
{
"compilerOptions": {
"strict": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true,
"noPropertyAccessFromIndexSignature": true
}
}
A migração para o modo strict do TypeScript não precisa ser traumática. Com planejamento adequado, estratégia gradual e ferramentas de suporte, você pode transformar seu código legado em uma base sólida e tipada. Lembre-se: o objetivo não é apenas ativar flags, mas criar uma cultura de código mais segura e previsível. Comece pequeno, documente cada passo e celebre cada módulo migrado com sucesso.
Referências
- Documentação oficial do TypeScript: strict — Guia completo sobre todas as flags do modo strict e como configurá-las individualmente
- TypeScript Handbook: Migrating from JavaScript — Estratégias oficiais para migração de código legado para TypeScript
- ESLint TypeScript Rules — Conjunto completo de regras ESLint para TypeScript que complementam o modo strict
- Husky Documentation — Guia oficial de configuração de hooks Git para verificação automática de tipos
- TypeScript Deep Dive: Strict Mode — Tutorial avançado sobre os benefícios e desafios do modo strict
- Migrating to TypeScript Strict Mode — Curso prático com exemplos reais de migração gradual
- TypeScript Strict Mode: A Practical Guide — Artigo técnico com dicas e truques para adoção do modo strict