DEV Community

Taise Soares
Taise Soares

Posted on

Compreendendo Generic em TypeScript

typescript generics

Olá pessoas! Bora entender/aprender sobre "genéricos" no TypeScript? Se você estiver querendo entender melhor, você veio ao lugar certo. Neste artigo, exploraremos o conceito de genéricos no TypeScript e forneceremos exemplos de código para ajudá-lo a entender melhor o tópico.

Mas afinal o que é uma Generic em TypeScript?

De acordo com a própria documentação, os genéricos são modelos de código que você pode definir e reutilizar em toda a base de código. Eles fornecem uma forma de informar a funções, classes ou interfaces que tipo você deseja usar ao chamá-las.

Em termos simples, os genéricos no TypeScript permitem que você crie um código flexível e reutilizável que pode funcionar com uma variedade de tipos de dados. Essencialmente, eles permitem definir funções, classes e interfaces que podem funcionar com qualquer tipo de dados, em vez de serem limitados a um tipo específico.

Usando um tipo genérico, você pode criar funções e classes que podem trabalhar com diferentes tipos de dados de maneira flexível e segura. Por exemplo, imagine que você precise criar uma função que calcule a soma de dois números. Em vez de definir os tipos de entrada como números, você pode usar um tipo genérico para criar uma função que aceite qualquer tipo de dados que possam ser somados.

Legal, mas como usar tipos genéricos em TypeScript?

Vamos dar uma olhada em alguns exemplos de código simples para ver como os genéricos funcionam no TypeScript. Primeiro, vamos criar uma função simples que analisa se dois números são iguais, retornando um booleano como resultado:

function isEqual(a: number, b: number): boolean {
  return a === b;
}

console.log(isEqual(2, 3)); // output: true
Enter fullscreen mode Exit fullscreen mode

Note que no exemplo acima, a função isEqual só irá funcionar, da forma correta, se os dois argumentos passados forem do tipo number.

Caso a gente tente passar qualquer outro tipo para a função isEqual, por exemplo um string, iremos ser atingidos pelo error:

console.log(add("2", "3")); // error: Argument of type '"2"' is not assignable to parameter of type 'number'.
Enter fullscreen mode Exit fullscreen mode

Agora você deve pensar: _"Pow super simples de solucionar, taca um any na tipagem e está tudo resolvido" _

Vamos seguir essa proposta no exemplo abaixo:

function isEqual(a: any, b: any): boolean {
  return a === b;
}

console.log(isEqual(2, 3)); // output: false
Enter fullscreen mode Exit fullscreen mode

Você deve acreditar que isso resolva seu problema, porém com isso criamos mais problemas ainda. Não é bom usar any em suas tipagens (isso daria um ótimo artigo não???), por hora, tudo que você precisa saber é que deve ser evitado em funções porque isso pode levar a problemas de segurança de tipo em tempo de execução e dificuldades na manutenção do código. A falta de especificidade em relação aos tipos de dados pode prejudicar a documentação do código e também a integração com outras bibliotecas e frameworks que exigem tipos específicos. Em resumo, é importante evitar o uso de any em funções no TypeScript para manter a segurança de tipo e a facilidade de manutenção do código.

Exemplo do que pode ocorrer com sua função:

function isEqual(a: any, b: any): boolean {
  return a === b;
}

console.log(isEqual(2, "2")); // output: false
Enter fullscreen mode Exit fullscreen mode

Note que estamos enviando um número e uma string, e nossa verificação analisa não somente se são iguais, mas se são do mesmo tipo, no caso sempre será um resultado false.

Então não posso usar any, o que vou usar?

Calma meu pequeno gafanhoto, é aqui que o genérico em TypeScript brilha.

A sintaxe para definir um tipo genérico em TypeScript é bastante simples. Você pode definir um tipo genérico adicionando um par de colchetes angulares <> após o nome do tipo ou da função, seguido por um identificador que será usado para se referir ao tipo genérico.

Por exemplo, aqui está nossa função simples que usa um tipo genérico para verificar se dois números são iguais:

function isEqual<T>(a: T, b: T): T {
  return a === b;
}
Enter fullscreen mode Exit fullscreen mode

Agora usamos a sintaxe <T> para indicar que a função isEqual pode funcionar com qualquer tipo de dados. O T é apenas um espaço reservado que pode ser substituído por qualquer tipo de dado. Também alteramos o tipo de retorno para T, para que a função retorne o mesmo tipo de dado que foi passado.

Com isso caso a gente tente passar tipos diferentes o typescript ira nos alertar da seguinte maneira:

console.log(add("Hello, ", 123)); // error: Argument of type 'number' is not assignable to parameter of type 'string'.
Enter fullscreen mode Exit fullscreen mode

Legal não é mesmo?

Usando Generic em classes

Agora que vimos como usar genéricos com funções, vamos dar uma olhada em como eles podem ser usados com classes. Bora para um exemplo bem simples de uma classe List simples:

class List {
  private data: any[] = [];

  push(item: any) {
    this.data.push(item);
  }

  pop() {
    return this.data.pop();
  }
}
Enter fullscreen mode Exit fullscreen mode

Essa classe List pode funcionar com qualquer tipo de dados, mas não é muito segura. Podemos acidentalmente colocar uma string na lista e, em seguida, tentar retirar um número, causando erros em tempo de execução.

Agora vamos reescrever a classe List usando genéricos:

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

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

  pop(): T | undefined {
    return this.data.pop();
  }
}
Enter fullscreen mode Exit fullscreen mode

Note que usamos a sintaxe <T> para indicar que a classe List pode funcionar com qualquer tipo de dados. Também alteramos a propriedade data para ser uma matriz de elementos T e atualizamos os métodos push e pop para aceitar e retornar valores T. Também adicionamos um tipo de retorno de T | undefined ao método pop, indicando que ele pode retornar undefined se a lista estiver vazia.

Agora, ao criarmos uma nova instância do List, podemos especificar o tipo de dado com o qual ela irá trabalhar:

const numberList = new List<number>();
numberList.push(1);
numberList.push(2);
console.log(numberList.pop()); // output: 2

const stringList = new List<string>();
stringList.push("hello");
stringList.push("world");
console.log(stringList.pop()); // output: "world"
Enter fullscreen mode Exit fullscreen mode

Ao usar genéricos, tornamos nossa classe List mais segura e flexível.

E como funciona, caso eu queria usar em interfaces?

Legal, vamos a mais um exemplo, agora vamos dar uma olhada em como os genéricos podem ser usados com interfaces. iremos criar uma interface que define um Dictionary:

interface Dictionary {
  [key: string]: any;
}
Enter fullscreen mode Exit fullscreen mode

Note que nossa interface Dictionary pode ser usada para representar um objeto que possui chaves de string e qualquer tipo de valor (olha o any ai de novo gente, vou escrever um artigo do porque não usar prometo). Mas e se quisermos criar um Dictionary que tenha chaves de um tipo específico e valores de um tipo específico?

Xiii ai complicou né? Calma pequeno gafanhoto, vamos reescrever a interface do Dictionary usando genéricos:

interface Dictionary<T> {
  [key: string]: T;
}
Enter fullscreen mode Exit fullscreen mode

Caracas, notaram como usamos a sintaxe para indicar que a interface Dictionary pode funcionar com qualquer tipo de dados? E Também alteramos a assinatura do índice para usar T como o tipo para os valores?

Ao criarmos uma nova instância do Dictionary, podemos especificar o tipo de dado com o qual ela irá trabalhar, pega só o exemplo:

const numberDict: Dictionary<number> = { one: 1, two: 2 };
console.log(numberDict.one); // output: 1

const stringDict: Dictionary<string> = { hello: "world", foo: "bar" };
console.log(stringDict.hello); // output: "world"
Enter fullscreen mode Exit fullscreen mode

Ao usar genéricos com interfaces, criamos uma maneira mais segura e flexível de definir nossas estruturas de dados, tornamos nosso código mais flexível e seguro em termos de tipo.

Bora de resumo

Os tipos genéricos são uma poderosa ferramenta em TypeScript que permitem criar funções e classes flexíveis e reutilizáveis que podem trabalhar com diferentes tipos de dados. Usando tipos genéricos, você pode tornar seu código mais genérico e flexível, além de aumentar a segurança do seu código.

É meu pequeno gafanhoto, espero que esse artigo tenha ajudado você a entender o conceito de tipos genéricos em TypeScript e como usá-los em seu código.

Se você tenha interesse em aprender mais sobre TypeScript, há muitos recursos disponíveis online, incluindo a documentação oficial do TypeScript. Com algum esforço e prática, você poderá usar tipos genéricos e outras funcionalidades do TypeScript para criar aplicativos mais seguros, eficientes e escaláveis.

Link documentação oficial
Link curso da microsoft

Top comments (0)

The discussion has been locked. New comments can't be added.