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
- Mesmo sem especificar explicitamente
<number>ou<string>, a inferência de tipo ocorre.identity(42)é inferido comonumber, eidentity("Hello")é inferido comostring.
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 queTdeve ser um tipo que possui a propriedadelength. Portanto, tipos sem a propriedadelengthnão serão aceitos.
Combinação com keyof
Ao combinar genéricos com keyof, você pode obter nomes de propriedades de forma segura em relação ao tipo.
1function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
2 return obj[key];
3}
4
5const person = { name: "Bob", age: 30 };
6const personName = getProperty(person, "name"); // string
7console.log(personName);
8
9// const error = getProperty(person, "unknown"); // Error
Ké restringido porkeyof T, indicando que somente as chaves existentes emTpodem ser especificadas. Especificar uma chave que não existe resultará em um erro de compilação.
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 tipoTusado 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
stringcom<T = string>. Se nenhum tipo for especificado explicitamente,Tserá do tipostring.
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 tipoT. 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
mergerecebe dois tipos diferentes,TeU, 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.