C

Typescript - Uma breve introdução - Generics

17 de dez. de 2020

Nos artigos anteriores nós vimos um pouco sobre tipos básicos, enums, type assertions , interfaces, type aliases, classes e type utilities. Recomendo dar uma conferida neles, caso não se sinta confortável com esses conceitos.

Nos últimos artigos nós aprendemos a criar tipos e logo em seguida a utilizar esses tipos nas nossas funções, classes ou variáveis, mas até aqui só usamos tipos "estáticos". O que eu quero dizer com "estáticos" é que se criarmos uma interface, ao longo do nosso código ela vai permanecer a mesma em todos os lugares que a gente utilizar(a não ser que a gente reescreva essa interface, mas isso não vem ao caso). Porém, uma das nossas atribuições como programadores e programadoras é escrever códigos que possam ser reaproveitados na maior parte dos casos. O ideal seria que um trecho de código feito para um dado X pudesse ser facilmente adaptado ou reutilizado para um dado Y no futuro. Existem muitas formas de alcançar esse feito e uma delas é utilizando generics.

O que é um generic?

Tá, mas o que é um generic? Basicamente, um generic é uma forma de passar algum "argumento" de tipo para uma função, classe ou interface fazendo com que eles possam ser utilizados de maneiras diferentes e/ou em cenários diferentes. Uma forma clara de entender um generic é pensando em uma função, então vamos ao exemplo.

function showInfo(value: string) {
  console.log({ value });
}

showInfo("Ok"); // {value: "Ok"}

No trecho de código acima temos uma função simples para mostrar logs, mas note que por hora ela recebe apenas um argumento do tipo string. E se eu quiser utilizar essa função para mostrar logs numéricos?

function showInfo(value: string | number) {
  console.log({ value });
}

showInfo(42); // {value: 42}

Problema resolvido! E se agora eu quiser mostrar logs de um tipo Person que tem a seguinte estrutura {name: string; age: number}? Eu poderia simplesmente adicionar esse tipo na minha função, mas note que isso já começa a se tornar algo muito trabalho… Outra solução seria tipar como any, mas assim vamos perder toda a segurança dos nossos tipos.É ai que entram os generics e a nossa função fica da seguinte forma.

function showInfo<MeuTipo>(value: MeuTipo) {
  console.log({ value });
}

interface Person {
  name: string;
  age: number;
}

const umaPessoa: Person = {
  name: "Cris",
  age: 25,
};

showInfo<Person>(umaPessoa);

Os generics são "argumentos" passados entre <> igualzinho ao que vimos com type utilities. No exemplo acima dizemos que a função showInfo vai receber um generic chamado MeuTipo e esse generic vai ser usado como tipo para o argumento value da minha função. É como se após invocar a função ela ficasse assim:

showInfo<Person>(umaPessoa);

// A função ficaria assim

function showInfo(value: Person) {
  console.log({ value });
}

No nosso exemplo eu utilizei MeuTipo, mas o comum é encontrar apenas letras como T, U, etc. Vale lembrar que podemos passar mais de um argumento pro nosso generic, basta separar por vírgula.

function showOtherValues<T, U>(value: T, otherValue: U): T {
  console.log({ value, otherValue });
  return value;
}

showOtherValues<String, Boolean>(false, false); // Erro! Argument of type 'boolean' is not assignable to parameter of type 'String'.

showOtherValues<String, Boolean>("Cris", false); // A função retorna a string 'Cris'

Mais um ponto importante é que caso nenhum tipo seja informado o typescript vai tentar fazer a inferência de tipos e utilizar any quando não conseguir.

function showInfo<MeuTipo>(value: MeuTipo) {
  console.log({ value });
}

showInfo(false); // Aqui value vai ser do tipo boolean
showInfo(42); // Aqui value vai ser do tipo number

Só consigo usar generics com funções?

Não, a gente consegue utilizar generics em classes e até para construir interfaces. A ideia é a mesma das funções, você passa os "argumentos" do seu generic logo após o nome da sua classe/interface. Vamos aos exemplos!

class GenericClass<T> {
  constructor(value: T) {
    this.value = value;
  }

  value: T;

  changeValue(v: T) {
    this.value = v;
  }

  getValue(): T {
    return this.value;
  }
}

let num = new GenericClass<Number>(0);

num.getValue(); // 0

num.changeValue(10); // 10

let str = new GenericClass<string>("");

str.getValue(); // ''

str.changeValue(10); // Erro! Argument of type 'number' is not assignable to parameter of type 'string'.

str.changeValue("Cris"); // Cris

Agora veja um exemplo utilizando interfaces.

interface GenericInterface<T> {
  value: T;
  getValue(): T;
}

const myObj: GenericInterface<Number> = {
  value: 0,
  getValue() {
    return this.value;
  },
};

Finalizando…

Generic é uma forma de escrever algo genérico, assim como o nome sugere… Utilizando essa funcionalidade podemos construir tipos fáceis de serem aproveitados para as nossas aplicações. Isso que vimos aqui é apenas o essencial para que você não se assuste quando ver um código como esse que acabei de tirar da tipagem do React.

Generics na tipagem do React

Isso é tudo pessoal!

Isso é tudo pessoal

Obrigado por chegar até aqui!! Espero que tenha conseguido te ajudar de alguma forma. 😊

Em breve irei escrever mais conteúdo sobre Typescript.

Então… Até mais!

Links importantes