Tipuri literale în TypeScript
Un tip literal este un tip care permite exact o singură valoare — de exemplu șirul "north" sau numărul 1 — în loc de orice șir sau orice număr.
Cum funcționează tipurile literale în TypeScript
Tipurile literale îți permit să spui exact ce valori poate avea o variabilă. Ele sunt mai precise decât tipurile generale string, number sau boolean.
Ele sunt cărămizile de bază pentru cod precis și sigur din punctul de vedere al tipurilor.
Concepte cheie:
- Literali de tip șir: valori de șir exacte, precum
"success" | "error" - Literali numerici: numere specifice, precum
1 | 2 | 3 - Literali booleeni: fie
true, fiefalse - Tipuri literale șablon: tipuri literale de șir construite cu sintaxa șirurilor șablon
Cazuri frecvente de utilizare:
- Definirea unui set fix de valori permise
- Crearea de uniuni discriminate
- Tratarea evenimentelor în siguranță din punctul de vedere al tipurilor
- Tiparea răspunsurilor de la un API
- Obiecte de configurare
Tipuri literale de șir
Un tip literal de șir reprezintă o singură valoare de șir. De obicei, mai multe astfel de tipuri se combină cu | într-o uniune de valori permise.
Exemplu
direction poate conține doar unul dintre cele patru șiruri. Orice alt șir, cum ar fi "northeast", este o eroare de compilare, atât la atribuirea către variabilă, cât și la apelarea funcției move.
direction cu tipul "north" | "south" | "east" | "west", atribuie-i "north" și apoi "south" și lasă comentate atribuirile de "northeast" și "up", împreună cu erorile pe care le-ar produce. Scrie o funcție move care primește aceeași uniune de literali și afișează Deplasare spre <direction>, apeleaz-o cu "east" și lasă comentat move("up") ca apel invalid.
Ieșire: program TypeScript și rezultatul capturat
// O variabilă cu un tip literal de șir
let direction: "north" | "south" | "east" | "west";
// Atribuiri valide
direction = "north";
direction = "south";
// Atribuirile invalide ar produce erori
// direction = "northeast"; // Eroare: tipul '"northeast"' nu poate fi atribuit tipului '"north" | "south" | "east" | "west"'
// direction = "up"; // Eroare: tipul '"up"' nu poate fi atribuit tipului '"north" | "south" | "east" | "west"'
// Folosirea tipurilor literale de șir în funcții
function move(direction: "north" | "south" | "east" | "west") {
console.log(`Deplasare spre ${direction}`);
}
move("east"); // Valid
// move("up"); // Eroare: argumentul de tip '"up"' nu poate fi atribuit parametrului de tip...
Tipuri literale numerice
La fel ca literalii de șir, tipurile literale numerice reprezintă valori numerice specifice.
Exemplu
diceRoll acceptă doar numerele de la 1 la 6. Pentru că Math.random() returnează un number obișnuit, funcția rollDice are nevoie de o aserțiune de tip prin care promite că returnează unul dintre cei șase literali. Valoarea afișată se schimbă la fiecare rulare.
diceRoll cu tipul 1 | 2 | 3 | 4 | 5 | 6, atribuie-i 1 și apoi 6 și lasă comentate atribuirile de 0, 7 și 2.5, împreună cu erorile lor. Scrie rollDice(), al cărei tip de retur este aceeași uniune și care returnează Math.floor(Math.random() * 6) + 1 convertit la acea uniune, apoi afișează Ai obținut un <result> (numărul este aleatoriu la fiecare rulare).
Ieșire: program TypeScript și rezultatul capturat
// O variabilă cu un tip literal numeric
let diceRoll: 1 | 2 | 3 | 4 | 5 | 6;
// Atribuiri valide
diceRoll = 1;
diceRoll = 6;
// Atribuirile invalide ar produce erori
// diceRoll = 0; // Eroare: tipul '0' nu poate fi atribuit tipului '1 | 2 | 3 | 4 | 5 | 6'
// diceRoll = 7; // Eroare: tipul '7' nu poate fi atribuit tipului '1 | 2 | 3 | 4 | 5 | 6'
// diceRoll = 2.5; // Eroare: tipul '2.5' nu poate fi atribuit tipului '1 | 2 | 3 | 4 | 5 | 6'
// Folosirea tipurilor literale numerice în funcții
function rollDice(): 1 | 2 | 3 | 4 | 5 | 6 {
return Math.floor(Math.random() * 6) + 1 as 1 | 2 | 3 | 4 | 5 | 6;
}
const result = rollDice();
console.log(`Ai obținut un ${result}`);
Tipuri literale booleene
Tipurile literale booleene se folosesc mai rar, pentru că există doar două valori booleene, dar sunt utile în anumite situații — de exemplu pentru a spune că o funcție returnează întotdeauna true.
Exemplu
alwaysSucceed trebuie să returneze literalul true. SuccessFlag și FailureFlag combină literali booleeni, de șir și numerici, iar processResult îi deosebește prin verificări simple de egalitate.
type YesOnly = true și o funcție alwaysSucceed() al cărei tip de retur este literalul true. Apoi declară SuccessFlag = true | "success" | 1 și FailureFlag = false | "failure" | 0, precum și o funcție processResult care primește oricare dintre ele și afișează Operațiunea a reușit atunci când valoarea este true, "success" sau 1, iar altfel afișează Operațiunea a eșuat. Apeleaz-o cu true, "success", 1 și false.
Ieșire: program TypeScript și rezultatul capturat
// Un tip care poate avea doar valoarea literală 'true'
type YesOnly = true;
// O funcție care trebuie să returneze true
function alwaysSucceed(): true {
// Returnează întotdeauna valoarea literală 'true'
return true;
}
// Literal boolean combinat cu alte tipuri
type SuccessFlag = true | "success" | 1;
type FailureFlag = false | "failure" | 0;
function processResult(result: SuccessFlag | FailureFlag) {
if (result === true || result === "success" || result === 1) {
console.log("Operațiunea a reușit");
} else {
console.log("Operațiunea a eșuat");
}
}
processResult(true); // "Operațiunea a reușit"
processResult("success"); // "Operațiunea a reușit"
processResult(1); // "Operațiunea a reușit"
processResult(false); // "Operațiunea a eșuat"
Tipuri literale cu obiecte
Tipurile literale pot fi folosite ca tipuri de proprietăți în interiorul tipurilor obiect. Astfel obții forme de obiect foarte precise.
Exemplu
HTTPSuccess și HTTPError limitează status și statusText la câteva valori literale fiecare. O simplă verificare de interval pe status nu restrânge uniunea, așa că exemplul folosește o mică gardă de tip isSuccess; în interiorul fiecărei ramuri, data sau error poate fi apoi folosit în siguranță.
HTTPSuccess (status: 200 | 201 | 204, statusText: "OK" | "Created" | "No Content", data: any) și HTTPError (status: 400 | 401 | 403 | 404 | 500, literalii statusText corespunzători, error: string), plus HTTPResponse ca uniune a lor. Scrie o gardă de tip isSuccess (response is HTTPSuccess) care verifică dacă statusul este în intervalul 200 și o funcție handleResponse care o folosește pentru a afișa Succes: <statusText> și datele în caz de succes, iar altfel afișează Eroare <status>: <statusText> și Mesaj: <error>. Apeleaz-o cu un răspuns 200 "OK" care transportă { username: "john_doe", email: "[email protected]" } și cu un răspuns 404 "Not Found" cu eroarea Utilizatorul nu a fost găsit în baza de date.
Ieșire: program TypeScript și rezultatul capturat
// Obiect cu valori literale pentru proprietăți
type HTTPSuccess = {
status: 200 | 201 | 204;
statusText: "OK" | "Created" | "No Content";
data: any;
};
type HTTPError = {
status: 400 | 401 | 403 | 404 | 500;
statusText: "Bad Request" | "Unauthorized" | "Forbidden" | "Not Found" | "Internal Server Error";
error: string;
};
type HTTPResponse = HTTPSuccess | HTTPError;
// Aici este nevoie de o gardă de tip: o simplă verificare de interval numeric nu restrânge uniunea
function isSuccess(response: HTTPResponse): response is HTTPSuccess {
return response.status >= 200 && response.status < 300;
}
function handleResponse(response: HTTPResponse) {
if (isSuccess(response)) {
console.log(`Succes: ${response.statusText}`);
console.log(response.data);
} else {
console.log(`Eroare ${response.status}: ${response.statusText}`);
console.log(`Mesaj: ${response.error}`);
}
}
// Exemplu de utilizare
const successResponse: HTTPSuccess = {
status: 200,
statusText: "OK",
data: { username: "john_doe", email: "[email protected]" }
};
const errorResponse: HTTPError = {
status: 404,
statusText: "Not Found",
error: "Utilizatorul nu a fost găsit în baza de date"
};
handleResponse(successResponse);
handleResponse(errorResponse);
Tipuri literale șablon
Începând cu TypeScript 4.1 poți construi tipuri literale de șir noi, combinându-le pe cele existente cu sintaxa șirurilor șablon, folosind accente grave și substituenți ${...}. Fiecare combinație a uniunilor din substituenți devine un membru al noii uniuni.
Exemplu
Exemplul pornește de la o uniune simplă DirectionAndDistance și ajunge la utilizări mai avansate: Capitalize pentru a construi nume de evenimente, un tip mapat care creează nume de getteri, infer pentru a extrage nume de parametri dintr-un șir de rută, un tip CssValue format din număr plus unitate și un constructor de interogări SQL tipat. Fișierul se încheie cu export {}, pentru ca tipul local EventTarget să nu intre în conflict cu tipul DOM încorporat.
Direction și Distance în DirectionAndDistance (`${Direction}-${Distance}`) și atribuie rute valide, cum ar fi "north-5km", unei variabile route. Apoi construiește EventName cu Capitalize, un tip mapat GetterName<T>/UserGetters peste un tip User, un tip recursiv ExtractRouteParams<T> care extrage numele :param dintr-o cale folosind infer, un tip CssValue de forma `${number}${CssUnit}`, un tip ApiUrl și o funcție query<T extends Table> al cărei tip de retur este `SELECT * FROM ${T}${string}`, apelată pentru tabelul users cu o WhereCondition. Lasă comentate atribuirile invalide. Nu se afișează nimic.
Adaugă export {}; la final, pentru ca tipul local EventTarget să nu intre în conflict cu globala DOM încorporată.
Ieșire: program TypeScript și rezultatul capturat
// Literali șablon de bază
type Direction = "north" | "south" | "east" | "west";
type Distance = "1km" | "5km" | "10km";
// Folosirea literalilor șablon pentru a le combina
type DirectionAndDistance = `${Direction}-${Distance}`;
// "north-1km" | "north-5km" | "north-10km" | "south-1km" | ...
let route: DirectionAndDistance;
route = "north-5km"; // Valid
route = "west-10km"; // Valid
// route = "north-2km"; // Eroare
// route = "5km-north"; // Eroare
// Manipulare avansată a șirurilor
type EventType = "click" | "hover" | "scroll";
type EventTarget = "button" | "link" | "div";
type EventName = `on${Capitalize<EventType>}${Capitalize<EventTarget>}`;
// "onClickButton" | "onClickLink" | "onClickDiv" | ...
// Acces dinamic la proprietăți
type User = {
id: number;
name: string;
email: string;
createdAt: Date;
};
type GetterName<T> = `get${Capitalize<string & keyof T>}`;
type UserGetters = {
[K in keyof User as GetterName<User>]: () => User[K];
};
// { getId: () => number; getName: () => string; ... }
// Potrivirea tiparelor de șiruri
type ExtractRouteParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractRouteParams<Rest>
: T extends `${string}:${infer Param}`
? Param
: never;
type Params = ExtractRouteParams<"/users/:userId/posts/:postId">; // "userId" | "postId"
// Unități și valori CSS
type CssUnit = 'px' | 'em' | 'rem' | '%' | 'vh' | 'vw';
type CssValue = `${number}${CssUnit}`;
let width: CssValue = '100px'; // Valid
let height: CssValue = '50%'; // Valid
// let margin: CssValue = '10'; // Eroare
// let padding: CssValue = '2ex'; // Eroare
// Versionarea unui API
type ApiVersion = 'v1' | 'v2' | 'v3';
type Endpoint = 'users' | 'products' | 'orders';
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiUrl = `https://api.example.com/${ApiVersion}/${Endpoint}`;
// Exemplu complex: constructor dinamic de interogări SQL
type Table = 'users' | 'products' | 'orders';
type Column<T extends Table> =
T extends 'users' ? 'id' | 'name' | 'email' | 'created_at' :
T extends 'products' ? 'id' | 'name' | 'price' | 'in_stock' :
T extends 'orders' ? 'id' | 'user_id' | 'total' | 'status' : never;
type WhereCondition<T extends Table> = {
[K in Column<T>]?: {
equals?: any;
notEquals?: any;
in?: any[];
};
};
function query<T extends Table>(
table: T,
where?: WhereCondition<T>
): `SELECT * FROM ${T}${string}` {
// Implementarea ar construi interogarea
return `SELECT * FROM ${table}` as const;
}
// Utilizare
const userQuery = query('users', {
name: { equals: 'John' },
created_at: { in: ['2023-01-01', '2023-12-31'] }
});
// Tip: "SELECT * FROM users WHERE ..."
// Transformăm fișierul în modul, ca tipul local EventTarget să nu intre în conflict cu globala încorporată
export {};
Bune practici
Recomandat:
- Folosește tipuri literale pentru seturi fixe de valori (opțiuni de tip enumerare, valori de configurare)
- Combină-le cu tipuri uniune pentru mai multă siguranță a tipurilor
- Folosește tipuri literale șablon pentru potrivirea tiparelor de șiruri
- Lasă inferența de tip să lucreze pentru tine acolo unde se poate
- Documentează ce înseamnă fiecare tip literal
De evitat:
- Folosirea excesivă a tipurilor literale atunci când un tip mai general ar fi mai potrivit
- Crearea de uniuni extrem de mari, care încetinesc verificarea tipurilor
- Folosirea literalilor de șir atunci când o enumerare ar fi mai potrivită
Considerații de performanță
- Uniunile mari de tipuri pot încetini verificarea tipurilor
- Tipurile literale șablon complexe pot crește timpul de compilare
- Folosește alias-uri de tip pentru a denumi tipurile literale complexe, în loc să le repeți
- Ține cont de limitele de adâncime a recursivității din TypeScript pentru tipurile literale șablon recursive