conditional types în TypeScript

Un tip care alege între două variante după o condiție: T extends U ? X : Y.

Sintaxă
type R = T extends U ? X : Y;

Ce face conditional types

Un tip condițional seamănă cu operatorul ? :, dar pentru tipuri: T extends string ? "text" : "altceva" dă "text" dacă T se potrivește cu string și "altceva" în rest. Este folosit aproape mereu cu un generic, pentru a calcula un tip din altul.

Când T este un union, condiția se aplică pe fiecare variantă separat și rezultatele se unesc (tip condițional distributiv). Așa funcționează Exclude: T extends U ? never : T elimină variantele care se potrivesc. Ca să tratezi union-ul ca întreg, pune ambele părți în paranteze pătrate: [T] extends [U].

Cu infer poți extrage o parte dintr-un tip în interiorul condiției (tipul elementelor unui tablou, rezultatul unei funcții). Multe tipuri utilitare sunt tipuri condiționale: Exclude, Extract, NonNullable, ReturnType, Awaited.

Exemple

Exemplul 1: Tip care depinde de argument

Pentru un id numeric, funcția returnează un number; pentru un text, un string. Conversia as din implementare este necesară, pentru că tsc nu poate verifica singur ramurile.

type Normalizat<T> = T extends string ? string : number;

function normalizeaza<T extends string | number>(id: T): Normalizat<T> {
    return (typeof id === "string" ? id.trim().toUpperCase() : Math.trunc(id)) as Normalizat<T>;
}

const a = normalizeaza(" ro-7 ");   // string
const b = normalizeaza(42.9);      // number
console.log(a, a.length, b, b.toFixed(1));

Rezultat

RO-7 4 42 42.0

Exemplul 2: Distribuție pe union

Condiția se aplică pe fiecare variantă: string și number trec, boolean devine never și dispare.

type DoarTextSauNumar<T> = T extends string | number ? T : never;
type Rezultat = DoarTextSauNumar<string | number | boolean>;    // string | number

type CaTablou<T> = T extends unknown ? T[] : never;
type Tablouri = CaTablou<string | number>;                    // string[] | number[]

const x: Rezultat = "Iași";
const t: Tablouri = [1, 2, 3];
console.log(x, t);

Rezultat

Iași [ 1, 2, 3 ]

Exemplul 3: Valoarea nu se potrivește cu ramura aleasă

EsteTablou<number> este false, deci valoarea true nu se potrivește.

type EsteTablou<T> = T extends unknown[] ? true : false;
const a: EsteTablou<string[]> = true;
const b: EsteTablou<number> = true;
console.log(a, b);

Eroarea afișată de tsc

exemplu.mts(3,7): error TS2322: Type 'true' is not assignable to type 'false'.

Greșeli frecvente

  • Te miri că EsteNever<never> scris ca T extends never ? true : false dă never. Un union gol se distribuie în nimic; scrie [T] extends [never].
  • Construiești tipuri condiționale foarte adânci. Erorile devin greu de citit; păstrează-le scurte și dă-le nume.

Comenzi înrudite

  • infer: În ramura extends a unui tip condițional, infer X „prinde” o parte din tip și o dă un nume.
  • Exclude: Exclude<T, U> elimină din union-ul T variantele care se potrivesc cu U.
  • Extract: Extract<T, U> păstrează din union-ul T doar variantele care se potrivesc cu U.
  • mapped types: Construiesc un tip nou trecând prin fiecare cheie a altui tip: { [K in keyof T]: … }.
  • generics (<T>): Parametri de tip: o funcție sau clasă scrisă o dată, care funcționează cu orice tip și păstrează tipul exact.