TypeScript: Noções básicas sobre genéricos no TypeScript
Última atualização: 2026-08-26
Os genéricos são o recurso mais poderoso do TypeScript — eles permitem que você escreva código que “funciona com vários tipos”, mantendo total segurança de tipos. Compreender os genéricos é um passo fundamental para dominar o TypeScript.
1. Por que os genéricos são necessários?
(1) O dilema de não ter genéricos
// Option 1: Use any — missing type information
function firstAny(arr: any[]): any {
return arr[0];
}
let n = firstAny([1, 2, 3]); // n is any — don't know if it's number
let s = firstAny(["a", "b"]); // s is any — don't know if it's string
// Option 2:Write a function for each type——Code Duplication
function firstNumber(arr: number[]): number { return arr[0]; }
function firstString(arr: string[]): string { return arr[0]; }
// Endless...
(2) Soluções genéricas
function first<T>(arr: T[]): T {
return arr[0];
}
let n = first([1, 2, 3]); // T Inferred as number → Back number
let s = first(["a", "b"]); // T Inferred as string → Back string
let b = first([true, false]); // T Inferred as boolean → Back boolean
console.log(n.toFixed(2)); // ✅ TypeScript knows n is number
console.log(s.toUpperCase()); // ✅ TypeScript knows s is string
<T> é um parâmetro de tipo — o que T representa depende do tipo do argumento passado quando a função é chamada. Ele é definido uma única vez, mas pode ser usado com vários tipos, e há informações precisas sobre o tipo disponíveis para cada caso.
2. Funções genéricas
(1) Sintaxe básica
function identity<T>(value: T): T {
return value;
}
// TypeScript Automatic Inference T
let num = identity(42); // T = number
let str = identity("hello"); // T = string
// You can also specify it explicitly T(Sometimes it is necessary to)
let result = identity<string>("hello");
(2) Vários parâmetros de tipo
function pair<A, B>(first: A, second: B): [A, B] {
return [first, second];
}
let p1 = pair("name", 42); // [string, number]
let p2 = pair(true, [1, 2, 3]); // [boolean, number[]]
(3) Genéricos e matrizes
function map<T, U>(arr: T[], transform: (item: T) => U): U[] {
return arr.map(transform);
}
let numbers = [1, 2, 3];
let strings = map(numbers, n => `No.${n}`);
// T = number, U = string → Back string[]
console.log(strings); // ["No.1", "No.2", "No.3"]
▶ Exemplo: Funções utilitárias genéricas
// Filter Array
function filter<T>(arr: T[], predicate: (item: T) => boolean): T[] {
return arr.filter(predicate);
}
// Searching an Array
function find<T>(arr: T[], predicate: (item: T) => boolean): T | undefined {
return arr.find(predicate);
}
// Array Chunking
function chunk<T>(arr: T[], size: number): T[][] {
let result: T[][] = [];
for (let i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, i + size));
}
return result;
}
let nums = [1, 2, 3, 4, 5, 6, 7];
let evens = filter(nums, n => n % 2 === 0);
let found = find(nums, n => n > 5);
let groups = chunk(nums, 3);
console.log("Even number:" + evens); // "Even number:2,4,6"
console.log("Found:" + found); // "Found:6"
console.log("Chunking:" + JSON.stringify(groups)); // "[[1,2,3],[4,5,6],[7]]"
Saída:
Even number:2,4,6
Found:6
Chunking:[[1,2,3],[4,5,6],[7]]
3. Interfaces genéricas
(1) Sintaxe básica
interface Box<T> {
value: T;
}
let stringBox: Box<string> = { value: "hello" };
let numberBox: Box<number> = { value: 42 };
(2) Função de descrição de interface genérica
interface Transformer<A, B> {
(input: A): B;
}
let toString: Transformer<number, string> = (n) => String(n);
let toLength: Transformer<string, number> = (s) => s.length;
console.log(toString(42)); // "42"
console.log(toLength("hello")); // 5
(3) O Padrão de Repositório de Descrição de Interface Genérica
interface Repository<T> {
findById(id: string): T | null;
findAll(): T[];
save(item: T): void;
delete(id: string): boolean;
}
interface User {
id: string;
name: string;
}
class UserRepo implements Repository<User> {
private data: User[] = [];
findById(id: string): User | null {
return this.data.find(u => u.id === id) ?? null;
}
findAll(): User[] {
return this.data;
}
save(item: User): void {
this.data.push(item);
}
delete(id: string): boolean {
let len = this.data.length;
this.data = this.data.filter(u => u.id !== id);
return this.data.length < len;
}
}
4. Classes genéricas
(1) Sintaxe básica
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
get size(): number {
return this.items.length;
}
}
let numStack = new Stack<number>();
numStack.push(1);
numStack.push(2);
numStack.push(3);
console.log(numStack.pop()); // 3
console.log(numStack.peek()); // 2
console.log(numStack.size); // 2
let strStack = new Stack<string>();
strStack.push("a");
strStack.push("b");
console.log(strStack.pop()); // "b"
(2) Membros estáticos de classes genéricas
class GenericClass<T> {
// static defaultValue: T; // ❌ Static members cannot reference a class's type parameters
// ✅ Static methods require their own type parameters.
static create<U>(value: U): GenericClass<U> {
let instance = new GenericClass<U>(value);
return instance;
}
constructor(public value: T) {}
}
let instance = GenericClass.create("hello"); // GenericClass<string>
T é de nível de instância — cada instância pode ter um T diferente. Os membros estáticos pertencem à própria classe, e não a nenhuma instância específica; portanto, não podem fazer referência aos parâmetros de tipo de uma instância.
5. Restrições genéricas (extends)
Por padrão, o tipo genérico T pode ser qualquer tipo — às vezes, isso é muito abrangente. Use extends para restringir T de modo que ele deva satisfazer determinadas condições:
(1) Restrito a uma interface específica
interface HasLength {
length: number;
}
// T Must have length Properties
function logLength<T extends HasLength>(value: T): void {
console.log(`Length:${value.length}`);
}
logLength("hello"); // ✅ string has length
logLength([1, 2, 3]); // ✅ The array contains length
logLength({ length: 10 }); // ✅ Objects include length
// logLength(42); // ❌ number None length
(2) Uma restrição é outro conceito genérico
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
let user = { name: "Charlie", age: 20, email: "xiao@example.com" };
console.log(getProperty(user, "name")); // ✅ "Charlie" —— K = "name"
console.log(getProperty(user, "age")); // ✅ 20 —— K = "age"
// getProperty(user, "phone"); // ❌ "phone" No user the key
(3) Restrições como construtores
function createInstance<T>(Constructor: new () => T): T {
return new Constructor();
}
class Dog {
bark() { return "Woof!"; }
}
let dog = createInstance(Dog);
console.log(dog.bark()); // "Woof!"
▶ Exemplo: Função genérica com restrição keyof
function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> {
let result = {} as Pick<T, K>;
for (let key of keys) {
result[key] = obj[key];
}
return result;
}
let user = { id: 1, name: "Charlie", email: "c@example.com", age: 25 };
let summary = pick(user, "id", "name");
// Type: { id: number; name: string }
console.log(summary.name); // "Charlie"
// summary.email; // ❌ email was not picked
Saída:
Charlie
▶ Exemplo: Fusão de objetos com segurança de tipos
function merge<T extends object, U extends object>(a: T, b: U): T & U {
return { ...a, ...b };
}
let defaults = { host: "localhost", port: 3000 };
let custom = { port: 8080, debug: true };
let config = merge(defaults, custom);
// Inference Types:{ host: string; port: number } & { port: number; debug: boolean }
// After simplification:{ host: string; port: number; debug: boolean }
console.log(config.host); // "localhost"
console.log(config.port); // 8080
console.log(config.debug); // true
Saída:
// Executed successfully
6. Tipos genéricos embutidos comuns
O TypeScript inclui muitos tipos genéricos úteis:
| Tipo | Função | Exemplo |
|---|---|---|
Array<T> |
Tipo de matriz | Array<number> |
Promise<T> |
Tipo de promessa | Promise<string> |
Record<K, V> |
Objeto de pares chave-valor | Record<string, number> |
Partial<T> |
Todos os atributos são opcionais | Partial<Config> |
Required<T> |
Todos os campos são obrigatórios | Required<Config> |
Readonly<T> |
Todas as propriedades são somente de leitura | Readonly<Config> |
Pick<T, K> |
Selecionar determinados atributos | Pick<User, "name" | "age"> |
Omit<T, K> |
Excluir determinados atributos | Omit<User, "email"> |
Exclude<T, U> |
Excluir dos tipos de união | Exclude<"a"|"b"|"c", "a"> |
Extract<T, U> |
Trecho sobre tipos de união | Extract<"a"|"b"|"c", "a"|"b"> |
ReturnType<T> |
Tipo de retorno da função | ReturnType<typeof fn> |
Parameters<T> |
Tupla de tipos de parâmetros de função | Parameters<typeof fn> |
// Examples of Actual Use
interface User {
id: number;
name: string;
email: string;
age: number;
}
// When creating a user id Not required
type CreateUser = Omit<User, "id">;
// All fields are optional when updating a user
type UpdateUser = Partial<User>;
// The user summary includes only some fields
type UserSummary = Pick<User, "id" | "name">;
let newUser: CreateUser = { name: "Charlie", email: "xiao@example.com", age: 20 };
let update: UpdateUser = { age: 21 };
let summary: UserSummary = { id: 1, name: "Charlie" };
❓ Perguntas Frequentes
P: Como os parâmetros de tipo genérico devem ser nomeados? R: Por convenção, use uma única letra maiúscula — T (Tipo), U/V/W (tipos subsequentes), K (Chave), V (Valor), E (Elemento). Para palavras múltiplas, use PascalCase:
TItem,TResponse. As equipes devem adotar uma convenção de nomenclatura consistente.
P: Quando é necessário especificar explicitamente um tipo genérico? R: Na maioria dos casos, o TypeScript consegue inferir tipos genéricos a partir dos parâmetros. É necessário especificá-los explicitamente nos seguintes cenários: (1) A função não possui parâmetros, ou os tipos dos parâmetros não permitem que T seja determinado; (2) A inferência é muito ampla (por exemplo, inferida como um tipo de união); (3) É necessário um controle mais preciso sobre o tipo de retorno da função.
P: Qual é a diferença entre a restrição genérica
extendse a herança de classesextends? R: A sintaxe é a mesma, mas a semântica difere. A restrição genéricaT extends HasLengthsignifica que “T deve ter, no mínimo, a estrutura de HasLength” — trata-se de uma relação de compatibilidade de tipos. A herança de classeclass Dog extends Animalsignifica “Dog é uma subclasse de Animal” — trata-se de uma relação orientada a objetos.
P: Qual é a diferença entre
Record<string, number>e{ [key: string]: number }? R: Eles são totalmente equivalentes.Recordé um tipo de utilitário genérico embutido que, essencialmente, é uma assinatura de índice.Record<string, number>é mais conciso, enquanto{ [key: string]: number }é mais flexível (permite adicionar propriedades conhecidas).
📖 Resumo
- Os genéricos
<T>são parâmetros de tipo — eles permitem que funções, interfaces e classes trabalhem com vários tipos, mantendo a segurança de tipos. - Função genérica:
function fn<T>(arg: T): T, em que T é inferido a partir dos argumentos no momento da chamada - Interfaces genéricas e classes genéricas: Adicione “T” ao final do nome,
interface Box<T>,class Stack<T> - As restrições genéricas
T extends typelimitam o intervalo de T para garantir que T satisfaça determinadas condições - O TypeScript inclui um amplo conjunto de tipos de utilidade genéricos embutidos: Record, Partial, Required, Readonly, Pick, Omit e outros
📝 Exercícios
- Problema básico (Dificuldade ⭐): Escreva uma função genérica
wrapInArray<T>(value: T): T[]que transforme um único valor em uma matriz. Teste-a com os tiposnumberestring. - Problema avançado (Dificuldade ⭐⭐): Escreva uma função genérica
pluck<T, K extends keyof T>(items: T[], key: K): T[K][]que extraia o valor de uma propriedade especificada de uma matriz de objetos. Por exemplo,pluck([{name: "a", age: 1}, {name: "b", age: 2}], "name")retorna["a", "b"]. - Desafio (Dificuldade: ⭐⭐⭐): Implemente uma classe genérica
DataStore<T extends { id: string }>que forneça métodos para salvar, localizar e excluir. Garanta que a propriedade id exista por meio de restrições genéricas. Crie uma instância deDataStore<{ id: string; title: string }>para testar a funcionalidade CRUD.