Generics e Tipos Avançados
Aula 4 de 7
Generics Básicos
Generics permitem criar componentes que funcionam com múltiplos tipos.
Função Genérica
function primeiroElemento<T>(arr: T[]): T | undefined {
return arr[0];
}
// Inferência automática
const num = primeiroElemento([1, 2, 3]); // number | undefined
const str = primeiroElemento(["a", "b"]); // string | undefined
// Especificação explícita
const item = primeiroElemento<number>([1, 2, 3]);
Múltiplos Type Parameters
function par<A, B>(a: A, b: B): [A, B] {
return [a, b];
}
const resultado = par("idade", 30); // [string, number]
Classe Genérica
class Pilha<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 tamanho(): number {
return this.items.length;
}
}
const pilhaNumeros = new Pilha<number>();
pilhaNumeros.push(1);
pilhaNumeros.push(2);
console.log(pilhaNumeros.pop()); // 2
Interface Genérica
interface Repositorio<T> {
buscar(id: string): Promise<T | null>;
listar(): Promise<T[]>;
criar(item: T): Promise<T>;
atualizar(id: string, item: Partial<T>): Promise<T>;
deletar(id: string): Promise<boolean>;
}
interface Usuario {
id: string;
nome: string;
email: string;
}
class RepositorioUsuario implements Repositorio<Usuario> {
async buscar(id: string): Promise<Usuario | null> {
return null;
}
async listar(): Promise<Usuario[]> {
return [];
}
async criar(item: Usuario): Promise<Usuario> {
return item;
}
async atualizar(id: string, item: Partial<Usuario>): Promise<Usuario> {
return { id, ...item } as Usuario;
}
async deletar(id: string): Promise<boolean> {
return true;
}
}
Generic Constraints (extends)
Restringir quais tipos são aceitos:
interface ComComprimento {
length: number;
}
function logComprimento<T extends ComComprimento>(item: T): void {
console.log(item.length);
}
logComprimento("texto"); // 5
logComprimento([1, 2, 3]); // 3
// logComprimento(123); // Erro: number não tem length
keyof Constraint
function getPropriedade<T, K extends keyof T>(obj: T, chave: K): T[K] {
return obj[chave];
}
const user = { nome: "Alice", idade: 30 };
getPropriedade(user, "nome"); // string
getPropriedade(user, "idade"); // number
// getPropriedade(user, "email"); // Erro: "email" não está em keyof T
Conditional Types
type IsString<T> = T extends string ? "sim" : "nao";
type A = IsString<string>; // "sim"
type B = IsString<number>; // "nao"
// Encadeado
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
type T0 = TypeName<string>; // "string"
type T1 = TypeName<true>; // "boolean"
Distribuição em Union Types
type ToArray<T> = T extends unknown ? T[] : never;
type Result = ToArray<string | number>;
// Result: string[] | number[] (não (string | number)[])
Mapped Types
type Opcional<T> = {
[P in keyof T]?: T[P];
};
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
// Exemplo:
interface Usuario {
nome: string;
idade: number;
}
type UsuarioOpcional = Opcional<Usuario>;
// { nome?: string; idade?: number }
Mapped Types com Modificadores
type Getters<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};
type Setters<T> = {
[P in keyof T as `set${Capitalize<string & P>}`]: (valor: T[P]) => void;
};
type UsuarioGetters = Getters<Usuario>;
// { getNome: () => string; getIdade: () => number }
Template Literal Types
type Evento = "click" | "focus" | "blur";
type Handler = `on${Capitalize<Evento>}`;
// "onClick" | "onFocus" | "onBlur"
type Margem = "margin";
type Lado = "top" | "right" | "bottom" | "left";
type PropriedadeMargem = `${Margem}${Capitalize<Lado>}`;
// "marginTop" | "marginRight" | "marginBottom" | "marginLeft"
infer Keyword
Usado em conditional types para inferir tipos dentro de padrões:
type Retorno<T> = T extends (...args: unknown[]) => infer R ? R : never;
type Fn = (x: number) => string;
type R = Retorno<Fn>; // string
// Inferir parâmetros
type Parametros<T> = T extends (...args: infer P) => unknown ? P : never;
type P = Parametros<(a: string, b: number) => void>;
// [string, number]
// Inferir tipo de Promise
type Await<T> = T extends Promise<infer U> ? U : T;
type AsyncResult = Await<Promise<string>>; // string
type SyncResult = Await<number>; // number
Lab: Helpers Genéricos
// Deep Partial — recursivo
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
interface Config {
db: { host: string; port: number };
cache: { ttl: number; enabled: boolean };
}
type PartialConfig = DeepPartial<Config>;
// Tudo opcional, recursivamente
// PickByType — seleciona props por tipo
type PickByType<T, V> = {
[P in keyof T as T[P] extends V ? P : never]: T[P];
};
type ApenasStrings = PickByType<Config, string>;
// { db: { host: string; port: number }; cache: { ttl: number; enabled: boolean } }
// Função type-safe com validação
function criarObjeto<T extends Record<string, unknown>>(
schema: Record<keyof T, string>,
dados: T,
): T {
for (const chave of Object.keys(schema)) {
if (typeof dados[chave] !== schema[chave]) {
throw new Error(`Tipo inválido para ${chave}`);
}
}
return dados;
}
npx tsc --noEmit
Generics + conditional types + mapped types formam a base do sistema de tipos mais expressivo do TypeScript. Use
inferpara extrair tipos de dentro de outros tipos.