Komplexní průvodce výkonnými mapovanými a podmíněnými typy v TypeScriptu, včetně praktických příkladů a pokročilých případů použití pro tvorbu robustních a typově bezpečných aplikací.
Zvládnutí mapovaných a podmíněných typů v TypeScriptu
TypeScript, nadmnožina JavaScriptu, nabízí výkonné funkce pro vytváření robustních a udržovatelných aplikací. Mezi těmito funkcemi vynikají mapované typy (Mapped Types) a podmíněné typy (Conditional Types) jako zásadní nástroje pro pokročilou manipulaci s typy. Tento průvodce poskytuje komplexní přehled těchto konceptů, zkoumá jejich syntaxi, praktické aplikace a pokročilé případy použití. Ať už jste zkušený vývojář v TypeScriptu, nebo teprve začínáte, tento článek vás vybaví znalostmi pro efektivní využití těchto funkcí.
Co jsou mapované typy?
Mapované typy umožňují vytvářet nové typy transformací existujících. Iterují přes vlastnosti existujícího typu a na každou vlastnost aplikují transformaci. To je zvláště užitečné pro vytváření variant existujících typů, například pro nastavení všech vlastností jako volitelných nebo pouze pro čtení.
Základní syntaxe
Syntaxe pro mapovaný typ je následující:
type NewType<T> = {
[K in keyof T]: Transformation;
};
T
: Vstupní typ, přes který chcete mapovat.K in keyof T
: Iteruje přes každý klíč ve vstupním typuT
.keyof T
vytvoří sjednocení (union) všech názvů vlastností vT
aK
představuje každý jednotlivý klíč během iterace.Transformation
: Transformace, kterou chcete aplikovat na každou vlastnost. Může to být přidání modifikátoru (jakoreadonly
nebo?
), změna typu nebo něco úplně jiného.
Praktické příklady
Vytvoření vlastností pouze pro čtení
Předpokládejme, že máte rozhraní reprezentující uživatelský profil:
interface UserProfile {
name: string;
age: number;
email: string;
}
Můžete vytvořit nový typ, kde jsou všechny vlastnosti pouze pro čtení:
type ReadOnlyUserProfile = {
readonly [K in keyof UserProfile]: UserProfile[K];
};
Nyní bude mít ReadOnlyUserProfile
stejné vlastnosti jako UserProfile
, ale všechny budou pouze pro čtení.
Vytvoření volitelných vlastností
Podobně můžete všechny vlastnosti nastavit jako volitelné:
type OptionalUserProfile = {
[K in keyof UserProfile]?: UserProfile[K];
};
OptionalUserProfile
bude mít všechny vlastnosti UserProfile
, ale každá z nich bude volitelná.
Modifikace typů vlastností
Můžete také modifikovat typ každé vlastnosti. Například můžete transformovat všechny vlastnosti na řetězce:
type StringifiedUserProfile = {
[K in keyof UserProfile]: string;
};
V tomto případě budou všechny vlastnosti v StringifiedUserProfile
typu string
.
Co jsou podmíněné typy?
Podmíněné typy umožňují definovat typy, které závisí na nějaké podmínce. Poskytují způsob, jak vyjádřit typové vztahy na základě toho, zda typ splňuje určité omezení. Je to podobné ternárnímu operátoru v JavaScriptu, ale pro typy.
Základní syntaxe
Syntaxe pro podmíněný typ je následující:
T extends U ? X : Y
T
: Typ, který je kontrolován.U
: Typ, který je rozšiřován typemT
(podmínka).X
: Typ, který se vrátí, pokudT
rozšiřujeU
(podmínka je pravdivá).Y
: Typ, který se vrátí, pokudT
nerozšiřujeU
(podmínka je nepravdivá).
Praktické příklady
Určení, zda je typ řetězec
Vytvořme typ, který vrátí string
, pokud je vstupní typ řetězec, a number
v opačném případě:
type StringOrNumber<T> = T extends string ? string : number;
type Result1 = StringOrNumber<string>; // string
type Result2 = StringOrNumber<number>; // number
type Result3 = StringOrNumber<boolean>; // number
Extrakce typu ze sjednocení (union)
Můžete použít podmíněné typy k extrakci specifického typu ze sjednocení typů. Například pro extrakci typů, které nejsou null:
type NonNullable<T> = T extends null | undefined ? never : T;
type Result4 = NonNullable<string | null | undefined>; // string
Zde, pokud je T
null
nebo undefined
, typ se stane never
, který je následně odfiltrován zjednodušením sjednocených typů v TypeScriptu.
Odvození typů (Inferring Types)
Podmíněné typy mohou být také použity k odvození typů pomocí klíčového slova infer
. To vám umožňuje extrahovat typ z komplexnější typové struktury.
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 příkladu ReturnType
extrahuje návratový typ funkce. Zkontroluje, zda je T
funkce, která přijímá jakékoli argumenty a vrací typ R
. Pokud ano, vrátí R
; jinak vrátí any
.
Kombinace mapovaných a podmíněných typů
Skutečná síla mapovaných a podmíněných typů spočívá v jejich kombinaci. To vám umožňuje vytvářet vysoce flexibilní a expresivní typové transformace.
Příklad: Deep Readonly
Běžným případem použití je vytvoření typu, který nastaví všechny vlastnosti objektu, včetně vnořených vlastností, jako pouze pro čtení. Toho lze dosáhnout pomocí rekurzivního podmíněné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>;
Zde DeepReadonly
rekurzivně aplikuje modifikátor readonly
na všechny vlastnosti a jejich vnořené vlastnosti. Pokud je vlastnost objekt, rekurzivně volá DeepReadonly
na tento objekt. V opačném případě jednoduše aplikuje modifikátor readonly
na vlastnost.
Příklad: Filtrování vlastností podle typu
Řekněme, že chcete vytvořit typ, který obsahuje pouze vlastnosti určitého typu. Toho můžete dosáhnout kombinací mapovaných a podmíněných typů.
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 příkladu FilterByType
iteruje přes vlastnosti T
a kontroluje, zda typ každé vlastnosti rozšiřuje U
. Pokud ano, zahrne vlastnost do výsledného typu; jinak ji vyloučí mapováním klíče na never
. Všimněte si použití "as" pro přemapování klíčů. Poté použijeme `Omit` a `keyof StringProperties` k odstranění řetězcových vlastností z původního rozhraní.
Pokročilé případy použití a vzory
Kromě základních příkladů lze mapované a podmíněné typy použít v pokročilejších scénářích k vytvoření vysoce přizpůsobitelných a typově bezpečných aplikací.
Distributivní podmíněné typy
Podmíněné typy jsou distributivní, když je kontrolovaný typ sjednocením typů (union type). To znamená, že podmínka je aplikována na každého člena sjednocení jednotlivě a výsledky jsou poté zkombinovány do nového sjednoceného typu.
type ToArray<T> = T extends any ? T[] : never;
type Result6 = ToArray<string | number>; // string[] | number[]
V tomto příkladu je ToArray
aplikován na každého člena sjednocení string | number
jednotlivě, což vede k string[] | number[]
. Pokud by podmínka nebyla distributivní, výsledek by byl (string | number)[]
.
Použití pomocných typů (Utility Types)
TypeScript poskytuje několik vestavěných pomocných typů, které využívají mapované a podmíněné typy. Tyto pomocné typy lze použít jako stavební bloky pro složitější typové transformace.
Partial<T>
: Nastaví všechny vlastnostiT
jako volitelné.Required<T>
: Nastaví všechny vlastnostiT
jako povinné.Readonly<T>
: Nastaví všechny vlastnostiT
jako pouze pro čtení.Pick<T, K>
: Vybere sadu vlastnostíK
zT
.Omit<T, K>
: Odstraní sadu vlastnostíK
zT
.Record<K, T>
: Vytvoří typ se sadou vlastnostíK
typuT
.Exclude<T, U>
: Vyloučí zT
všechny typy, které jsou přiřaditelné kU
.Extract<T, U>
: Extrahuje zT
všechny typy, které jsou přiřaditelné kU
.NonNullable<T>
: Vyloučínull
aundefined
zT
.Parameters<T>
: Získá parametry funkčního typuT
.ReturnType<T>
: Získá návratový typ funkčního typuT
.InstanceType<T>
: Získá typ instance konstruktoru funkčního typuT
.
Tyto pomocné typy jsou výkonné nástroje, které mohou zjednodušit složité manipulace s typy. Například můžete zkombinovat Pick
a Partial
k vytvoření typu, který nastaví pouze určité vlastnosti jako volitelné:
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 příkladu má OptionalDescriptionProduct
všechny vlastnosti Product
, ale vlastnost description
je volitelná.
Použití typů šablonových literálů (Template Literal Types)
Typy šablonových literálů umožňují vytvářet typy založené na řetězcových literálech. Lze je použít v kombinaci s mapovanými a podmíněnými typy k vytvoření dynamických a expresivních typových transformací. Například můžete vytvořit typ, který přidá prefix ke všem názvům vlastností:
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 příkladu bude mít PrefixedSettings
vlastnosti data_apiUrl
a data_timeout
.
Doporučené postupy a úvahy
- Udržujte to jednoduché: Ačkoli jsou mapované a podmíněné typy výkonné, mohou také váš kód zkomplikovat. Snažte se udržovat své typové transformace co nejjednodušší.
- Používejte pomocné typy: Kdykoli je to možné, využívejte vestavěné pomocné typy TypeScriptu. Jsou dobře otestované a mohou zjednodušit váš kód.
- Dokumentujte své typy: Jasně dokumentujte své typové transformace, zejména pokud jsou složité. Pomůže to ostatním vývojářům pochopit váš kód.
- Testujte své typy: Používejte kontrolu typů v TypeScriptu k zajištění, že vaše typové transformace fungují podle očekávání. Můžete psát jednotkové testy k ověření chování vašich typů.
- Zvažte výkon: Složité typové transformace mohou ovlivnit výkon kompilátoru TypeScriptu. Mějte na paměti složitost vašich typů a vyhýbejte se zbytečným výpočtům.
Závěr
Mapované typy a podmíněné typy jsou výkonné funkce v TypeScriptu, které vám umožňují vytvářet vysoce flexibilní a expresivní typové transformace. Zvládnutím těchto konceptů můžete zlepšit typovou bezpečnost, udržovatelnost a celkovou kvalitu vašich aplikací v TypeScriptu. Od jednoduchých transformací, jako je nastavení vlastností jako volitelných nebo pouze pro čtení, až po složité rekurzivní transformace a podmíněnou logiku, tyto funkce poskytují nástroje, které potřebujete k vytváření robustních a škálovatelných aplikací. Pokračujte v prozkoumávání a experimentování s těmito funkcemi, abyste odemkli jejich plný potenciál a stali se zdatnějším vývojářem v TypeScriptu.
Jak pokračujete na své cestě s TypeScriptem, nezapomeňte využívat bohatství dostupných zdrojů, včetně oficiální dokumentace TypeScriptu, online komunit a open-source projektů. Přijměte sílu mapovaných a podmíněných typů a budete dobře vybaveni k řešení i těch nejnáročnějších problémů souvisejících s typy.