Genéricos em TypeScript

Genéricos em TypeScript

Este artigo explica genéricos em TypeScript.

YouTube Video

Genéricos em TypeScript

Genéricos em TypeScript são um recurso que permite definir funções, classes e interfaces reutilizáveis e seguras quanto ao tipo ao parametrizar os tipos. Ao usar genéricos, você pode escrever código que não depende de tipos específicos, permitindo realizar as mesmas operações em diversos tipos.

Noções básicas sobre Genéricos

Genéricos funcionam como templates que aceitam tipos como argumentos, permitindo que funções e classes lidem com diferentes tipos.

Funções Genéricas

A seguir está um exemplo de função com seus tipos de argumentos especificados usando genéricos.

1function identity<T>(value: T): T {
2    return value;
3}
4
5console.log(identity<number>(42));       // 42
6console.log(identity<string>("Hello"));  // Hello
  • T é um argumento de tipo genérico que representa os tipos dos argumentos e do valor de retorno da função. O tipo real é determinado quando a função é chamada.
  • Ao especificar explicitamente <number> ou <string>, você está determinando o tipo.

Tipos genéricos funcionam sem especificação explícita porque o TypeScript faz inferência de tipos.

1function identity<T>(value: T): T {
2    return value;
3}
4
5console.log(identity(42));       // 42
6console.log(identity("Hello"));  // Hello

Restrições em Genéricos

Ao impor restrições nos genéricos, você pode limitar para que aceitem apenas tipos específicos.

1function loggingIdentity<T extends { length: number }>(arg: T): T {
2    console.log(arg.length);
3    return arg;
4}
5
6loggingIdentity("Hello");  // 5
7loggingIdentity([1, 2, 3]);  // 3
8
9// loggingIdentity(42);  // Error: number does not have a length property.
  • Ao especificar T extends { length: number }, estamos indicando que T deve ser um tipo que possui a propriedade length. Portanto, tipos sem a propriedade length não serão aceitos.

Classes Genéricas

Classes também podem ser definidas usando genéricos. Classes genéricas oferecem tipos flexíveis para propriedades e métodos.

 1class Box<T> {
 2    private _value: T;
 3
 4    constructor(value: T) {
 5        this._value = value;
 6    }
 7
 8    public getValue(): T {
 9        return this._value;
10    }
11
12    public setValue(value: T): void {
13        this._value = value;
14    }
15}
16
17const numberBox = new Box<number>(100);
18console.log(numberBox.getValue());  // 100
19
20const stringBox = new Box<string>("Hello");
21console.log(stringBox.getValue());  // Hello
  • Box<T> declara o tipo T usado dentro da classe como genérico. Isso permite que a mesma classe seja reutilizada para diferentes tipos.

Interfaces Genéricas

Genéricos também podem ser usados com interfaces.

 1interface Pair<T, U> {
 2    first: T;
 3    second: U;
 4}
 5
 6const numberStringPair: Pair<number, string> = { first: 1, second: "One" };
 7console.log(numberStringPair);  // { first: 1, second: 'One' }
 8
 9const booleanArrayPair: Pair<boolean, number[]> = { first: true, second: [1, 2, 3] };
10console.log(booleanArrayPair);  // { first: true, second: [ 1, 2, 3 ] }
  • Ao especificar dois tipos genéricos com Pair<T, U>, você pode definir um objeto com uma combinação de diferentes tipos.

Argumentos de Tipo Padrão

Também é possível especificar um tipo padrão para os argumentos genéricos.

1function createArray<T = string>(length: number, value: T): T[] {
2    return Array(length).fill(value);
3}
4
5console.log(createArray(3, "a"));   // ['a', 'a', 'a']
6console.log(createArray(3, 100));   // [100, 100, 100]
  • Estamos definindo o argumento de tipo padrão como string com <T = string>. Se nenhum tipo for especificado explicitamente, T será do tipo string.

Apelidos de Tipos Genéricos

Genéricos também podem ser usados como apelidos de tipo (type).

 1type Result<T> = {
 2    success: boolean;
 3    data: T;
 4};
 5
 6const successResult: Result<number> = { success: true, data: 42 };
 7const errorResult: Result<string> = { success: false, data: "Error occurred" };
 8
 9console.log(successResult);  // { success: true, data: 42 }
10console.log(errorResult);    // { success: false, data: 'Error occurred' }
  • Result<T> representa um objeto resultado que contém dados do tipo T. Dessa forma, você pode criar apelidos de tipo flexíveis usando genéricos.

Múltiplos Tipos Genéricos

Ao usar múltiplos tipos genéricos, você pode definir funções e classes ainda mais versáteis.

1function merge<T, U>(obj1: T, obj2: U): T & U {
2    return { ...obj1, ...obj2 };
3}
4
5const person = { name: "Alice" };
6const job = { title: "Engineer" };
7
8const merged = merge(person, job);
9console.log(merged);  // { name: 'Alice', title: 'Engineer' }
  • A função merge recebe dois tipos diferentes, T e U, e os combina para retornar um novo objeto.

Resumo

  • Genéricos permitem código reutilizável e seguro em relação a tipos, tratando tipos como parâmetros.
  • Ao usar genéricos em funções, classes e interfaces, é possível escrever lógica flexível que trata diversos tipos.
  • Ao adicionar restrições aos argumentos de tipo ou definir argumentos de tipo padrão, você pode controlar o escopo dos genéricos.

Ao usar genéricos, você pode escrever código independente de tipo e de uso geral, aproveitando ao máximo o poderoso sistema de tipos do TypeScript.

Você pode acompanhar o artigo acima usando o Visual Studio Code em nosso canal do YouTube. Por favor, confira também o canal do YouTube.

YouTube Video