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

TYPESCRIPT
// 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

TYPESCRIPT
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
📌 Conceito-chave: <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

TYPESCRIPT
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

TYPESCRIPT
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

TYPESCRIPT
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

TYPESCRIPT
// 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]]"
▶ Experimente

Saída:

TEXT 📖 Somente leitura
Even number:2,4,6
Found:6
Chunking:[[1,2,3],[4,5,6],[7]]


3. Interfaces genéricas

(1) Sintaxe básica

TYPESCRIPT
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

TYPESCRIPT
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

TYPESCRIPT
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

TYPESCRIPT
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

TYPESCRIPT
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>
📌 Motivo: O parâmetro de tipo da classe 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

TYPESCRIPT
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

TYPESCRIPT
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

TYPESCRIPT
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

TYPESCRIPT
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
▶ Experimente

Saída:

TEXT 📖 Somente leitura
Charlie

▶ Exemplo: Fusão de objetos com segurança de tipos

TYPESCRIPT
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
▶ Experimente

Saída:

TEXT 📖 Somente leitura
// 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&lt;string&gt;
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>
TYPESCRIPT
// 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 extends e a herança de classes extends? R: A sintaxe é a mesma, mas a semântica difere. A restrição genérica T extends HasLength significa 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 classe class Dog extends Animal significa “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

📝 Exercícios

  1. 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 tipos number e string.
  2. 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"].
  3. 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 de DataStore<{ id: string; title: string }> para testar a funcionalidade CRUD.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%