TypeScript 5.x: as features que você deveria usar agora

1. Decorators nativos: o padrão que finalmente chegou

O TypeScript 5.0 trouxe os decorators nativos como uma feature estável, alinhada com a proposta do ECMAScript. Diferente dos decorators experimentais (experimentalDecorators), a nova implementação não requer flags de compilação e segue o padrão ES.

// Antes (experimental)
function log(target: any, key: string) {
    console.log(`Chamando ${key}`);
}

// Agora (nativo - TypeScript 5.x)
function log(_target: any, context: ClassMethodDecoratorContext) {
    const methodName = String(context.name);
    return function(this: any, ...args: any[]) {
        console.log(`Chamando ${methodName}`);
        return this.method.apply(this, args);
    };
}

class Servico {
    @log
    processar(dados: string) {
        return `Processado: ${dados}`;
    }
}

Para migrar, remova experimentalDecorators do tsconfig.json e atualize a assinatura dos decorators. Casos de uso incluem injeção de dependência, logging e validação.

2. Modificadores de acesso em construtores: parameter properties

Com parameter properties, você pode declarar e inicializar propriedades diretamente no construtor, reduzindo boilerplate significativamente.

// Abordagem tradicional
class Usuario {
    public nome: string;
    private email: string;

    constructor(nome: string, email: string) {
        this.nome = nome;
        this.email = email;
    }
}

// Com parameter properties (TypeScript 5.x)
class Usuario {
    constructor(
        public nome: string,
        private email: string
    ) {}
}

Combinado com decorators, você pode adicionar validação automática:

function validarEmail(_target: any, context: ParameterDecoratorContext) {
    // Implementação de validação
}

class Cadastro {
    constructor(
        public nome: string,
        @validarEmail public email: string
    ) {}
}

3. const type parameters: inferência literal sem surpresas

O modificador const em type parameters preserva tipos literais em funções genéricas, evitando que arrays e objetos sejam ampliados para tipos mais genéricos.

// Sem const - perde informações literais
function criarRota<T extends string[]>(paths: T): T {
    return paths;
}
const rota1 = criarRota(['/home', '/about']); // tipo: string[]

// Com const - preserva literais
function criarRota<const T extends string[]>(paths: T): T {
    return paths;
}
const rota2 = criarRota(['/home', '/about']); // tipo: ['/home', '/about']

Isso é útil para funções de roteamento e configuração tipada:

function configurar<const T extends Record<string, string>>(config: T): T {
    return config;
}
const config = configurar({ api: 'v1', timeout: '5000' });
// tipo: { api: 'v1'; timeout: '5000' } - não string genérico

4. satisfies operator: validação sem perda de tipo

O operador satisfies valida se um valor satisfaz um tipo, mas mantém o tipo literal inferido, não o substituindo pelo tipo de validação.

type CoresValidas = 'red' | 'blue' | 'green';

// Anotação explícita perde o literal
const cor1: CoresValidas = 'red'; // tipo: CoresValidas

// satisfies mantém o literal
const cor2 = 'red' satisfies CoresValidas; // tipo: 'red'

// Exemplo prático: objeto de configuração
type Config = Record<string, string | number>;
const config = {
    url: 'https://api.example.com',
    timeout: 5000,
    retries: 3
} satisfies Config;
// config.url ainda é string literal, não string genérico

5. Melhorias em enum: união de tipos e strings

Enums tradicionais podem ser substituídos por alternativas mais modernas que oferecem melhor inferência de tipos.

// Enum tradicional
enum Status {
    Ativo = 'ativo',
    Inativo = 'inativo'
}

// Alternativa moderna com const enum
const enum Status {
    Ativo = 'ativo',
    Inativo = 'inativo'
}

// Alternativa com union types e as const
const Status = {
    Ativo: 'ativo',
    Inativo: 'inativo'
} as const;

type Status = typeof Status[keyof typeof Status];
// equivalente a: 'ativo' | 'inativo'

Use enum tradicional quando precisar de runtime checks ou compatibilidade com código legado. Para novos projetos, prefira as const com union types.

6. using declarations e disposição de recursos

O TypeScript 5.2 introduziu suporte a using declarations, que gerenciam automaticamente o ciclo de vida de recursos através de Symbol.dispose e Symbol.asyncDispose.

class ConexaoBanco {
    [Symbol.dispose]() {
        console.log('Fechando conexão...');
        // Lógica de fechamento
    }

    query(sql: string) {
        return `Resultado de: ${sql}`;
    }
}

function processarDados() {
    using db = new ConexaoBanco();
    const resultado = db.query('SELECT * FROM usuarios');
    // Conexão é fechada automaticamente ao sair do escopo
    return resultado;
}

Para operações assíncronas:

class AsyncResource {
    async [Symbol.asyncDispose]() {
        await this.cleanup();
    }

    private async cleanup() {
        // Limpeza assíncrona
    }
}

async function usarRecurso() {
    await using recurso = new AsyncResource();
    // Recurso é liberado automaticamente
}

7. Isolated declarations e modo verbatimModuleSyntax

isolatedDeclarations permite gerar arquivos .d.ts sem análise cruzada entre arquivos, melhorando o tempo de compilação em projetos grandes.

// tsconfig.json
{
    "compilerOptions": {
        "isolatedDeclarations": true,
        "verbatimModuleSyntax": true
    }
}

verbatimModuleSyntax controla como as importações são processadas, garantindo que tipos sejam importados como import type e valores como import, melhorando a compatibilidade com bundlers.

8. Type-only imports/exports: performance e clareza

A sintaxe import type e export type permite eliminar código morto durante o tree-shaking, melhorando o bundle final.

// Antes
import { Usuario, funcaoUtil } from './modulo';
// Ambos são incluídos no bundle, mesmo que Usuario seja apenas tipo

// Depois (TypeScript 5.x)
import type { Usuario } from './modulo';
import { funcaoUtil } from './modulo';
// Usuario é removido durante a compilação

Boas práticas para migração:

// modulo.ts
export type Usuario = { nome: string };
export function funcaoUtil() { return 'util'; }

// Re-export type
export type { Usuario } from './modulo';
export { funcaoUtil } from './modulo';

Essas features do TypeScript 5.x oferecem melhorias significativas em produtividade, segurança de tipos e performance. Adotá-las progressivamente em projetos existentes ou desde o início em novos projetos resulta em código mais limpo, seguro e eficiente.

Referências