constrângeri generice (extends) în TypeScript

T extends Tip limitează tipurile permise pentru T, ca funcția să poată folosi proprietățile lui Tip.

Sintaxă
function f<T extends Tip>(x: T) { ... }
function g<T, K extends keyof T>(obiect: T, cheie: K): T[K] { ... }

Ce face constrângeri generice (extends)

Un generic simplu, <T>, poate fi orice, deci în funcție nu poți folosi nicio proprietate a lui. O constrângere, <T extends { length: number }>, spune: „T poate fi orice tip care are length”. Acum poți citi x.length, iar apelurile cu un tip fără length sunt erori.

Rezultatul păstrează totuși tipul exact al argumentului: dacă dai un string, T este string, nu { length: number }. Asta este diferența față de un parametru cu tipul constrângerii.

O combinație foarte folosită este <T, K extends keyof T>: K poate fi doar numele unei proprietăți din T, iar T[K] este tipul acelei proprietăți (vezi keyof și indexed access).

Exemple

Exemplul 1: Orice tip care are length

function celMaiLung<T extends { length: number }>(a: T, b: T): T {
    return a.length >= b.length ? a : b;
}

console.log(celMaiLung("Iași", "Timișoara"));
console.log(celMaiLung([1, 2, 3], [4, 5]));
const rezultat = celMaiLung("Ana", "Ioana");   // tipul rămâne string
console.log(rezultat.toUpperCase());

Rezultat

Timișoara
[ 1, 2, 3 ]
IOANA

Exemplul 2: Cheie care trebuie să existe

K extends keyof T acceptă doar "nume", "oras" sau "nota". Rezultatul are tipul proprietății alese: pentru "nota" este number[], deci merge în Math.max.

function coloana<T, K extends keyof T>(lista: T[], cheie: K): T[K][] {
    return lista.map((x) => x[cheie]);
}

const elevi = [
    { nume: "Mihai", oras: "Cluj", nota: 8 },
    { nume: "Ana", oras: "Iași", nota: 10 },
    { nume: "Ioana", oras: "Brașov", nota: 9 },
];

const nume = coloana(elevi, "nume");     // string[]
const note = coloana(elevi, "nota");     // number[]
console.log(nume, coloana(elevi, "oras"));
console.log(note, Math.max(...note));

Rezultat

[ 'Mihai', 'Ana', 'Ioana' ] [ 'Cluj', 'Iași', 'Brașov' ]
[ 8, 10, 9 ] 10

Exemplul 3: Argument care nu respectă constrângerea

function celMaiLung<T extends { length: number }>(a: T, b: T): T {
    return a.length >= b.length ? a : b;
}
console.log(celMaiLung(10, 200));

Eroarea afișată de tsc

exemplu.mts(4,24): error TS2345: Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.

Greșeli frecvente

  • Scrii function f<T>(x: T) { return x.length; } fără constrângere. tsc dă Property ‘length’ does not exist on type ‘T’.
  • Returnezi un obiect nou de tipul constrângerii dintr-o funcție care promite T. tsc refuză, pentru că T poate fi un subtip cu mai multe proprietăți.

Comenzi înrudite

  • generics (<T>): Parametri de tip: o funcție sau clasă scrisă o dată, care funcționează cu orice tip și păstrează tipul exact.
  • keyof: keyof T dă union-ul numelor de proprietăți ale lui T, de exemplu „nume” | „oras”.
  • indexed access (T[K]): T[„cheie”] dă tipul unei proprietăți din T; T[number] dă tipul elementelor unui tablou.
  • extends: O clasă moștenește câmpurile și metodele altei clase; o interfață preia proprietățile altor interfețe.
  • conditional types: Un tip care alege între două variante după o condiție: T extends U ? X : Y.