Komplexný sprievodca výkonnými mapovanými a podmienenými typmi v TypeScript, vrátane praktických príkladov a pokročilých prípadov použitia na tvorbu robustných a typovo bezpečných aplikácií.
Zvládnutie mapovaných a podmienených typov v TypeScript
TypeScript, nadmnožina JavaScriptu, ponúka výkonné funkcie na vytváranie robustných a udržiavateľných aplikácií. Medzi týmito funkciami vynikajú Mapované typy (Mapped Types) a Podmienené typy (Conditional Types) ako základné nástroje pre pokročilú manipuláciu s typmi. Tento sprievodca poskytuje komplexný prehľad týchto konceptov, skúma ich syntax, praktické aplikácie a pokročilé prípady použitia. Či už ste skúsený TypeScript vývojár alebo len začínate svoju cestu, tento článok vás vybaví znalosťami na efektívne využitie týchto funkcií.
Čo sú mapované typy?
Mapované typy umožňujú vytvárať nové typy transformáciou existujúcich. Iterujú cez vlastnosti existujúceho typu a na každú vlastnosť aplikujú transformáciu. Toto je obzvlášť užitočné pri vytváraní variácií existujúcich typov, ako napríklad nastavenie všetkých vlastností na voliteľné alebo iba na čítanie.
Základná syntax
Syntax pre mapovaný typ je nasledovná:
type NewType<T> = {
[K in keyof T]: Transformation;
};
T
: Vstupný typ, cez ktorý chcete mapovať.K in keyof T
: Iteruje cez každý kľúč vo vstupnom typeT
.keyof T
vytvára zjednotenie (union) všetkých názvov vlastností vT
aK
predstavuje každý jednotlivý kľúč počas iterácie.Transformation
: Transformácia, ktorú chcete aplikovať na každú vlastnosť. Môže to byť pridanie modifikátora (akoreadonly
alebo?
), zmena typu alebo niečo úplne iné.
Praktické príklady
Nastavenie vlastností iba na čítanie
Povedzme, že máte rozhranie (interface) reprezentujúce profil používateľa:
interface UserProfile {
name: string;
age: number;
email: string;
}
Môžete vytvoriť nový typ, kde sú všetky vlastnosti iba na čítanie:
type ReadOnlyUserProfile = {
readonly [K in keyof UserProfile]: UserProfile[K];
};
Teraz bude mať ReadOnlyUserProfile
rovnaké vlastnosti ako UserProfile
, ale všetky budú iba na čítanie.
Nastavenie vlastností na voliteľné
Podobne môžete nastaviť všetky vlastnosti ako voliteľné:
type OptionalUserProfile = {
[K in keyof UserProfile]?: UserProfile[K];
};
OptionalUserProfile
bude mať všetky vlastnosti typu UserProfile
, ale každá z nich bude voliteľná.
Úprava typov vlastností
Môžete tiež upraviť typ každej vlastnosti. Napríklad, môžete transformovať všetky vlastnosti na reťazce (string):
type StringifiedUserProfile = {
[K in keyof UserProfile]: string;
};
V tomto prípade budú všetky vlastnosti v StringifiedUserProfile
typu string
.
Čo sú podmienené typy?
Podmienené typy umožňujú definovať typy, ktoré závisia od podmienky. Poskytujú spôsob, ako vyjadriť vzťahy medzi typmi na základe toho, či typ spĺňa určité obmedzenie. Je to podobné ternárnemu operátoru v JavaScripte, ale pre typy.
Základná syntax
Syntax pre podmienený typ je nasledovná:
T extends U ? X : Y
T
: Typ, ktorý sa kontroluje.U
: Typ, ktorýT
rozširuje (podmienka).X
: Typ, ktorý sa vráti, akT
rozširujeU
(podmienka je pravdivá).Y
: Typ, ktorý sa vráti, akT
nerozširujeU
(podmienka je nepravdivá).
Praktické príklady
Určenie, či je typ reťazec
Vytvorme typ, ktorý vráti string
, ak je vstupný typ reťazec, a number
v opačnom prípade:
type StringOrNumber<T> = T extends string ? string : number;
type Result1 = StringOrNumber<string>; // string
type Result2 = StringOrNumber<number>; // number
type Result3 = StringOrNumber<boolean>; // number
Extrahovanie typu zo zjednotenia (Union)
Môžete použiť podmienené typy na extrahovanie špecifického typu zo zjednotenia (union type). Napríklad na extrahovanie typov, ktoré nemôžu byť null:
type NonNullable<T> = T extends null | undefined ? never : T;
type Result4 = NonNullable<string | null | undefined>; // string
Tu, ak je T
rovné null
alebo undefined
, typ sa stane never
, ktorý je potom odfiltrovaný zjednodušením zjednotených typov v TypeScript.
Odvodzovanie typov (Inferring)
Podmienené typy sa dajú použiť aj na odvodzovanie typov pomocou kľúčového slova infer
. To vám umožní extrahovať typ z komplexnejšej typovej štruktúry.
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
function myFunction(x: number): string {
return x.toString();
}
type Result5 = ReturnType<typeof myFunction>; // string
V tomto príklade ReturnType
extrahuje návratový typ funkcie. Kontroluje, či T
je funkcia, ktorá prijíma akékoľvek argumenty a vracia typ R
. Ak áno, vráti R
; inak vráti any
.
Kombinácia mapovaných a podmienených typov
Skutočná sila mapovaných a podmienených typov pochádza z ich kombinácie. To vám umožňuje vytvárať vysoko flexibilné a expresívne transformácie typov.
Príklad: Hĺbkové Readonly
Bežným prípadom použitia je vytvorenie typu, ktorý nastaví všetky vlastnosti objektu, vrátane vnorených vlastností, iba na čítanie. To sa dá dosiahnuť pomocou rekurzívneho podmieneného typu.
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
interface Company {
name: string;
address: {
street: string;
city: string;
};
}
type ReadonlyCompany = DeepReadonly<Company>;
Tu DeepReadonly
rekurzívne aplikuje modifikátor readonly
na všetky vlastnosti a ich vnorené vlastnosti. Ak je vlastnosť objektom, rekurzívne volá DeepReadonly
na tento objekt. V opačnom prípade jednoducho aplikuje modifikátor readonly
na vlastnosť.
Príklad: Filtrovanie vlastností podľa typu
Povedzme, že chcete vytvoriť typ, ktorý obsahuje iba vlastnosti špecifického typu. Môžete to dosiahnuť kombináciou mapovaných a podmienených typov.
type FilterByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
interface Person {
name: string;
age: number;
isEmployed: boolean;
}
type StringProperties = FilterByType<Person, string>; // { name: string; }
type NonStringProperties = Omit<Person, keyof StringProperties>;
V tomto príklade FilterByType
iteruje cez vlastnosti T
a kontroluje, či typ každej vlastnosti rozširuje U
. Ak áno, zahrnie vlastnosť do výsledného typu; inak ju vylúči mapovaním kľúča na never
. Všimnite si použitie "as" na premapovanie kľúčov. Následne použijeme `Omit` a `keyof StringProperties` na odstránenie reťazcových vlastností z pôvodného rozhrania.
Pokročilé prípady použitia a vzory
Okrem základných príkladov sa mapované a podmienené typy dajú použiť v pokročilejších scenároch na vytváranie vysoko prispôsobiteľných a typovo bezpečných aplikácií.
Distributívne podmienené typy
Podmienené typy sú distributívne, keď je kontrolovaný typ zjednotením (union type). To znamená, že podmienka sa aplikuje na každého člena zjednotenia jednotlivo a výsledky sa potom spoja do nového zjednoteného typu.
type ToArray<T> = T extends any ? T[] : never;
type Result6 = ToArray<string | number>; // string[] | number[]
V tomto príklade sa ToArray
aplikuje na každého člena zjednotenia string | number
jednotlivo, čo vedie k string[] | number[]
. Ak by podmienka nebola distributívna, výsledok by bol (string | number)[]
.
Používanie pomocných typov (Utility Types)
TypeScript poskytuje niekoľko vstavaných pomocných typov, ktoré využívajú mapované a podmienené typy. Tieto pomocné typy sa dajú použiť ako stavebné kamene pre komplexnejšie transformácie typov.
Partial<T>
: Urobí všetky vlastnostiT
voliteľnými.Required<T>
: Urobí všetky vlastnostiT
povinnými.Readonly<T>
: Urobí všetky vlastnostiT
iba na čítanie.Pick<T, K>
: Vyberie sadu vlastnostíK
zT
.Omit<T, K>
: Odstráni sadu vlastnostíK
zT
.Record<K, T>
: Vytvorí typ so sadou vlastnostíK
typuT
.Exclude<T, U>
: Vylúči zT
všetky typy, ktoré sú priraditeľné kU
.Extract<T, U>
: Extrahujte zT
všetky typy, ktoré sú priraditeľné kU
.NonNullable<T>
: Vylúčinull
aundefined
zT
.Parameters<T>
: Získa parametre funkčného typuT
.ReturnType<T>
: Získa návratový typ funkčného typuT
.InstanceType<T>
: Získa typ inštancie konštruktorovej funkcie typuT
.
Tieto pomocné typy sú výkonné nástroje, ktoré môžu zjednodušiť komplexné manipulácie s typmi. Napríklad, môžete skombinovať Pick
a Partial
na vytvorenie typu, ktorý robí iba určité vlastnosti voliteľnými:
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
interface Product {
id: number;
name: string;
price: number;
description: string;
}
type OptionalDescriptionProduct = Optional<Product, "description">;
V tomto príklade má OptionalDescriptionProduct
všetky vlastnosti produktu Product
, ale vlastnosť description
je voliteľná.
Používanie typov šablónových literálov (Template Literal Types)
Typy šablónových literálov vám umožňujú vytvárať typy založené na reťazcových literáloch. Môžu byť použité v kombinácii s mapovanými a podmienenými typmi na vytváranie dynamických a expresívnych transformácií typov. Napríklad, môžete vytvoriť typ, ktorý pred všetky názvy vlastností pridá špecifický reťazec:
type Prefix<T, P extends string> = {
[K in keyof T as `${P}${string & K}`]: T[K];
};
interface Settings {
apiUrl: string;
timeout: number;
}
type PrefixedSettings = Prefix<Settings, "data_">;
V tomto príklade bude mať PrefixedSettings
vlastnosti data_apiUrl
a data_timeout
.
Najlepšie postupy a úvahy
- Udržujte to jednoduché: Hoci sú mapované a podmienené typy výkonné, môžu tiež váš kód skomplikovať. Snažte sa udržať vaše transformácie typov čo najjednoduchšie.
- Používajte pomocné typy: Kedykoľvek je to možné, využívajte vstavané pomocné typy TypeScriptu. Sú dobre otestované a môžu zjednodušiť váš kód.
- Dokumentujte svoje typy: Jasne dokumentujte svoje transformácie typov, najmä ak sú zložité. Pomôže to ostatným vývojárom pochopiť váš kód.
- Testujte svoje typy: Použite kontrolu typov v TypeScript na zabezpečenie, že vaše transformácie typov fungujú podľa očakávania. Môžete písať jednotkové testy na overenie správania vašich typov.
- Zvážte výkon: Komplexné transformácie typov môžu ovplyvniť výkon vášho TypeScript kompilátora. Dávajte pozor na zložitosť vašich typov a vyhýbajte sa zbytočným výpočtom.
Záver
Mapované typy a Podmienené typy sú výkonné funkcie v TypeScript, ktoré vám umožňujú vytvárať vysoko flexibilné a expresívne transformácie typov. Zvládnutím týchto konceptov môžete zlepšiť typovú bezpečnosť, udržiavateľnosť a celkovú kvalitu vašich aplikácií v TypeScript. Od jednoduchých transformácií, ako je nastavenie vlastností na voliteľné alebo iba na čítanie, až po komplexné rekurzívne transformácie a podmienenú logiku, tieto funkcie poskytujú nástroje, ktoré potrebujete na budovanie robustných a škálovateľných aplikácií. Pokračujte v skúmaní a experimentovaní s týmito funkciami, aby ste odomkli ich plný potenciál a stali sa zdatnejším vývojárom v TypeScript.
Ako pokračujete na svojej ceste s TypeScriptom, nezabudnite využívať bohatstvo dostupných zdrojov, vrátane oficiálnej dokumentácie TypeScriptu, online komunít a open-source projektov. Prijmite silu mapovaných a podmienených typov a budete dobre vybavení na zvládnutie aj tých najnáročnejších problémov súvisiacich s typmi.