Genéricos avançados do TypeScript explicados com exemplos

Genéricos em TypeScript fornecem uma maneira de criar componentes de código reutilizáveis ​​e flexíveis trabalhando com uma variedade de tipos de dados. Genéricos avançados levam esse conceito mais adiante ao introduzir recursos adicionais como restrições, valores padrão e vários tipos, que permitem que os desenvolvedores escrevam códigos mais robustos e seguros para tipos. Neste artigo, exemplos serão usados ​​para explorar esses conceitos avançados em genéricos.

Restrições genéricas

Restrições limitam os tipos que um genérico pode aceitar. Isso garante que o tipo passado para uma função ou classe genérica atenda a certos critérios. Por exemplo, uma restrição pode ser usada para garantir que o tipo genérico tenha uma propriedade ou método específico.

function getLength<T extends { length: number }>(arg: T): number {
    return arg.length;
}

const stringLength = getLength("TypeScript");
const arrayLength = getLength([1, 2, 3]);

Neste exemplo, a restrição <T extends { length: number }> garante que o argumento passado para getLength tenha uma propriedade length.

Vários Genéricos

TypeScript permite o uso de vários tipos genéricos na mesma função, classe ou interface. Isso é útil ao trabalhar com pares de valores ou outras estruturas de dados envolvendo vários tipos.

function pair<T, U>(first: T, second: U): [T, U] {
    return [first, second];
}

const stringNumberPair = pair("TypeScript", 2024);

Esta função, pair, aceita dois tipos genéricos diferentes, T e U, e retorna uma tupla contendo ambos os tipos.

Tipos genéricos padrão

Genéricos em TypeScript também podem ter tipos padrão. Isso é útil quando você quer que um genérico tenha um tipo de fallback se nenhum tipo específico for fornecido.

function identity<T = string>(value: T): T {
    return value;
}

const defaultString = identity("Hello");  // T is string
const customNumber = identity<number>(100);  // T is number

Neste exemplo, se nenhum tipo for passado para identity, o padrão será string.

Usando Genéricos com Interfaces

Genéricos podem ser usados ​​com interfaces para definir estruturas complexas onde os tipos não são fixos. Isso adiciona flexibilidade a como os dados são gerenciados.

interface Container<T> {
    value: T;
}

const stringContainer: Container<string> = { value: "Hello" };
const numberContainer: Container<number> = { value: 42 };

A interface Container foi projetada para armazenar um valor de qualquer tipo, permitindo diferentes tipos de contêineres com tipos específicos.

Classes genéricas

Classes em TypeScript também podem ser genéricas. Isso é especialmente útil ao projetar classes que funcionam com vários tipos de dados, como classes de armazenamento ou coleção de dados.

class DataStore<T> {
    private data: T[] = [];

    add(item: T): void {
        this.data.push(item);
    }

    getAll(): T[] {
        return this.data;
    }
}

const stringStore = new DataStore<string>();
stringStore.add("Hello");
stringStore.add("TypeScript");

const numberStore = new DataStore<number>();
numberStore.add(42);

Neste exemplo, a classe DataStore funciona com qualquer tipo de dado, fornecendo uma maneira segura de armazenar e recuperar elementos.

Conclusão

Genéricos avançados em TypeScript são uma ferramenta poderosa para escrever código flexível, reutilizável e com segurança de tipo. Ao usar restrições, vários tipos, valores padrão e genéricos em classes e interfaces, os desenvolvedores podem escrever código mais complexo e robusto. Entender e utilizar esses conceitos avançados permite maior flexibilidade e garante a segurança de tipo em todos os aplicativos.