Técnicas Avançadas e Padrões Práticos em TypeScript

Extração de Tipos a partir de Funções

Em cenários de desenvolvimento real, muitas vezes precisamos reutilizar as definições de tipos de uma função já existente sem a necesssidade de rdeeclará-las manualmente. O TypeScript oferece utilitários poderosos para extrair essas informações através de inferência.

function processarRegistro(dados: { uuid: string; ativo: boolean }): { status: string; timestamp: number }[] {
    // Lógica interna simplificada
    return [{ status: "sucesso", timestamp: Date.now() }];
}

// Extraindo o tipo do retorno
type ResultadoProcessamento = ReturnType<typeof processarRegistro>;
/* 
type ResultadoProcessamento = {
    status: string;
    timestamp: number;
}[]
*/

// Extraindo o tipo dos parâmetros
type ParametrosProcessamento = Parameters<typeof processarRegistro>;
/*
type ParametrosProcessamento = [dados: {
    uuid: string;
    ativo: boolean;
}]
*/

Tipagem em Requisições Assíncronas

Ao trabalhar com APIs, é fundamental garantir que a resposta do servidor esteja devidamente tipada para evitar erros em tempo de execução. O uso de Generics permite criar estruturas flexíveis para diferentes endpoints.

import axios from 'axios';

interface RespostaPadrao<T = any> {
    data: T;
    error: boolean;
    meta: { version: string };
}

interface PerfilUsuario {
    nickname: string;
    level: number;
}

// Abordagem com Generics dinâmicos
export async function buscarPerfil<T>() {
    return axios.get<RespostaPadrao<T>>('/api/profile')
        .then(res => res.data)
        .catch(err => console.error("Erro na requisição", err));
}

async function execucao() {
    // O tipo 'usuario' conterá a estrutura de PerfilUsuario dentro de 'data'
    const usuario = await buscarPerfil<PerfilUsuario>();
}

// Abordagem com tipo pré-definido na função
export function buscarPerfilEspecífico() {
    return axios.get<RespostaPadrao<PerfilUsuario>>('/api/profile');
}

A Importância da Ordem em Sobrecargas de Funções

O TypeScript resolve sobrecargas de funções seguindo a ordem de declaração. Se uma definição genérica for colocada antes de uma definição específica, a versão mais específica nunca será alcançada.

// Padrão Incorreto: O tipo 'unknown' captura tudo antes dos tipos específicos
declare function manipular(input: unknown): unknown;
declare function manipular(input: HTMLInputElement): number;

const inputEl: HTMLInputElement = {} as any;
const res = manipular(inputEl); // res é inferido como 'unknown'

// Padrão Correto: Do mais específico para o mais genérico
declare function processarElemento(el: HTMLButtonElement): string;
declare function processarElemento(el: HTMLElement): number;
declare function processarElemento(el: any): any;

const botao: HTMLButtonElement = {} as any;
const resultado = processarElemento(botao); // resultado é inferido como 'string'

Combinação e Extensão de Declarações

O TypeScript permite que múltiplas declarações com o mesmo nome coexistam, mesclando suas propriedades. Isso é útil para estender bibliotecas ou organizar código complexo.

Mesclagem de Interfaces

interface AppConfig {
    apiHost: string;
}

interface AppConfig {
    timeout: number;
}

const config: AppConfig = {
    apiHost: "https://api.exemplo.com",
    timeout: 5000
};

Classes e Namespaces

É possível combinar classes com namespaces para adicionar propriedades estáticas ou tipos internos de forma organizada.

class GerenciadorVendas {}

namespace GerenciadorVendas {
    export interface Detalhes {
        idVenda: number;
        valor: number;
    }
    export const versao = "1.0.0";
}

let info: GerenciadorVendas.Detalhes;
console.log(GerenciadorVendas.versao);

Checagem de Tipos em Arquivos JavaScript

Em projetos legados ou em transição para TypeScript, é possível utilizar o motor do TS para validar arquivos .js através de anotações JSDoc e da diretiva @ts-check.

// @ts-check

/** @type {string} */
let identificador;

identificador = "ID-123"; // Correto
// identificador = 456;   // Erro detectado pelo TypeScript: Type 'number' is not assignable to type 'string'.

Utilização de Bibliotecas de Tipos Utilitários

Para manipulações avançadas de tipos, como mesclagem profunda ou validação de campos obrigatórios parciais, bibliotecas como o type-fest são altamente recomendadas.

import type { Merge, RequireAtLeastOne } from 'type-fest';

interface BaseInfo {
    id: string;
    criadoEm: Date;
}

type UpdateInfo = {
    id: number; // Substituindo o tipo da base
    atualizadoEm: Date;
};

// Mesclando tipos onde UpdateInfo sobrescreve propriedades conflitantes de BaseInfo
type DadosCompletos = Merge<BaseInfo, UpdateInfo>;

// Exemplo: Garantir que ao menos um meio de contato seja fornecido
type Contato = {
    email?: string;
    telefone?: string;
    whatsapp?: string;
};

const registro: RequireAtLeastOne<Contato, 'email' | 'telefone'> = {
    email: "dev@exemplo.com"
};

Desafios de Tipagem

Para desenvolvedores que desejam aprofundar seu conhecimento no sistema de tipos (Type System), existem comunidades dedicadas a desafios de lógica pura em TypeScript, como o type-challenges. Embora casos extremos de "ginástica de tipos" sejam raros no dia a dia corporativo, praticar essas técnicas melhora significativamente a capacidade de criar abstrações robustas e seguras em aplicações de grande escala.

Tags: TypeScript Web Development Software Architecture javascript

Publicado em 6-30 19:35