Átfogó útmutató a TypeScript hatékony Mapped és Conditional típusaihoz, gyakorlati példákkal és haladó felhasználási esetekkel a robusztus és típusbiztos alkalmazások létrehozásához.
A TypeScript Mapped és Conditional típusainak mesteri szintű használata
A TypeScript, a JavaScript egy szuperhalmaza, hatékony funkciókat kínál robusztus és karbantartható alkalmazások létrehozásához. Ezen funkciók közül a Mapped Types (leképezett típusok) és a Conditional Types (feltételes típusok) kiemelkednek mint a haladó típuskészítés alapvető eszközei. Ez az útmutató átfogó áttekintést nyújt ezekről a koncepciókról, bemutatva szintaxisukat, gyakorlati alkalmazásaikat és haladó felhasználási eseteiket. Akár tapasztalt TypeScript fejlesztő, akár csak most kezdi útját, ez a cikk felvértezi Önt azzal a tudással, amellyel hatékonyan kiaknázhatja ezeket a funkciókat.
Mik azok a Mapped típusok?
A Mapped típusok lehetővé teszik új típusok létrehozását meglévők átalakításával. Végigiterálnak egy meglévő típus tulajdonságain, és minden tulajdonságra egy transzformációt alkalmaznak. Ez különösen hasznos meglévő típusok variációinak létrehozásakor, például az összes tulajdonság opcionálissá vagy írásvédetté tételénél.
Alapvető szintaxis
Egy Mapped típus szintaxisa a következő:
type NewType<T> = {
[K in keyof T]: Transformation;
};
T
: A bemeneti típus, amelyen a leképezést el szeretné végezni.K in keyof T
: Végigiterál aT
bemeneti típus minden kulcsán. Akeyof T
egy uniót hoz létre aT
összes tulajdonságnevéből, és aK
képviseli az egyes kulcsokat az iteráció során.Transformation
: Az a transzformáció, amelyet minden tulajdonságra alkalmazni szeretne. Ez lehet egy módosító hozzáadása (példáulreadonly
vagy?
), a típus megváltoztatása, vagy valami egészen más.
Gyakorlati példák
Tulajdonságok írásvédetté tétele
Tegyük fel, hogy van egy interfészünk, amely egy felhasználói profilt reprezentál:
interface UserProfile {
name: string;
age: number;
email: string;
}
Létrehozhat egy új típust, ahol minden tulajdonság írásvédett:
type ReadOnlyUserProfile = {
readonly [K in keyof UserProfile]: UserProfile[K];
};
Most a ReadOnlyUserProfile
ugyanazokkal a tulajdonságokkal rendelkezik, mint a UserProfile
, de mindegyik írásvédett lesz.
Tulajdonságok opcionálissá tétele
Hasonlóképpen, minden tulajdonságot opcionálissá tehet:
type OptionalUserProfile = {
[K in keyof UserProfile]?: UserProfile[K];
};
Az OptionalUserProfile
a UserProfile
összes tulajdonságával rendelkezik, de mindegyik opcionális lesz.
Tulajdonságtípusok módosítása
Módosíthatja az egyes tulajdonságok típusát is. Például az összes tulajdonságot stringgé alakíthatja:
type StringifiedUserProfile = {
[K in keyof UserProfile]: string;
};
Ebben az esetben a StringifiedUserProfile
összes tulajdonsága string
típusú lesz.
Mik azok a Conditional típusok?
A Conditional típusok lehetővé teszik, hogy feltételtől függő típusokat definiáljunk. Módot biztosítanak a típuskapcsolatok kifejezésére annak alapján, hogy egy típus megfelel-e egy adott megszorításnak. Ez hasonló a JavaScript ternáris operátorához, de típusokra alkalmazva.
Alapvető szintaxis
Egy Conditional típus szintaxisa a következő:
T extends U ? X : Y
T
: Az ellenőrzött típus.U
: A típus, amelyet aT
kiterjeszt (a feltétel).X
: A visszaadandó típus, ha aT
kiterjeszti azU
-t (a feltétel igaz).Y
: A visszaadandó típus, ha aT
nem terjeszti ki azU
-t (a feltétel hamis).
Gyakorlati példák
Annak meghatározása, hogy egy típus string-e
Hozzuk létre egy típust, amely string
-et ad vissza, ha a bemeneti típus string, egyébként pedig number
-t:
type StringOrNumber<T> = T extends string ? string : number;
type Result1 = StringOrNumber<string>; // string
type Result2 = StringOrNumber<number>; // number
type Result3 = StringOrNumber<boolean>; // number
Típus kinyerése unióból
Feltételes típusokat használhat egy adott típus kinyerésére egy unió típusból. Például a nem-null értékű típusok kinyerésére:
type NonNullable<T> = T extends null | undefined ? never : T;
type Result4 = NonNullable<string | null | undefined>; // string
Itt, ha a T
értéke null
vagy undefined
, a típus never
lesz, amelyet a TypeScript unió típus egyszerűsítése kiszűr.
Típusok kikövetkeztetése (Inferring)
A feltételes típusok az infer
kulcsszóval típusok kikövetkeztetésére is használhatók. Ez lehetővé teszi egy típus kinyerését egy összetettebb típusstruktúrából.
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
Ebben a példában a ReturnType
kinyeri egy függvény visszatérési típusát. Ellenőrzi, hogy a T
egy olyan függvény-e, amely bármilyen argumentumot fogad és egy R
típust ad vissza. Ha igen, akkor R
-t ad vissza; egyébként any
-t.
A Mapped és Conditional típusok kombinálása
A Mapped és Conditional típusok valódi ereje a kombinálásukban rejlik. Ez lehetővé teszi rendkívül rugalmas és kifejező típus-transzformációk létrehozását.
Példa: Mélyen írásvédett (Deep Readonly)
Gyakori felhasználási eset egy olyan típus létrehozása, amely egy objektum minden tulajdonságát, beleértve a beágyazott tulajdonságokat is, írásvédetté tesz. Ezt egy rekurzív feltételes típussal lehet elérni.
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>;
Itt a DeepReadonly
rekurzívan alkalmazza a readonly
módosítót minden tulajdonságra és azok beágyazott tulajdonságaira. Ha egy tulajdonság objektum, rekurzívan meghívja a DeepReadonly
-t azon az objektumon. Ellenkező esetben egyszerűen csak a readonly
módosítót alkalmazza a tulajdonságra.
Példa: Tulajdonságok szűrése típus szerint
Tegyük fel, hogy szeretne létrehozni egy típust, amely csak egy adott típusú tulajdonságokat tartalmaz. Ezt a Mapped és Conditional típusok kombinálásával érheti el.
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>;
Ebben a példában a FilterByType
végigiterál a T
tulajdonságain, és ellenőrzi, hogy az egyes tulajdonságok típusa kiterjeszti-e az U
-t. Ha igen, akkor bevonja a tulajdonságot az eredménytípusba; ellenkező esetben kizárja azt a kulcs never
-re való leképezésével. Figyelje meg az „as” használatát a kulcsok újra-leképezéséhez. Ezután az `Omit` és a `keyof StringProperties` segítségével távolítjuk el a string tulajdonságokat az eredeti interfészből.
Haladó felhasználási esetek és minták
Az alapvető példákon túl a Mapped és Conditional típusok haladóbb forgatókönyvekben is használhatók, hogy rendkívül testreszabható és típusbiztos alkalmazásokat hozzunk létre.
Disztributív feltételes típusok
A feltételes típusok disztributívak, ha az ellenőrzött típus egy unió típus. Ez azt jelenti, hogy a feltétel az unió minden tagjára külön-külön alkalmazódik, és az eredményeket egy új unió típusban egyesítik.
type ToArray<T> = T extends any ? T[] : never;
type Result6 = ToArray<string | number>; // string[] | number[]
Ebben a példában a ToArray
a string | number
unió minden tagjára külön-külön alkalmazódik, ami a string[] | number[]
eredményt adja. Ha a feltétel nem lenne disztributív, az eredmény (string | number)[]
lett volna.
Segédtípusok (Utility Types) használata
A TypeScript számos beépített segédtípust (utility type) biztosít, amelyek a Mapped és Conditional típusokra épülnek. Ezek a segédtípusok építőelemként használhatók összetettebb típus-transzformációkhoz.
Partial<T>
: AT
minden tulajdonságát opcionálissá teszi.Required<T>
: AT
minden tulajdonságát kötelezővé teszi.Readonly<T>
: AT
minden tulajdonságát írásvédetté teszi.Pick<T, K>
: Kiválasztja aK
tulajdonságok halmazát aT
-ből.Omit<T, K>
: Eltávolítja aK
tulajdonságok halmazát aT
-ből.Record<K, T>
: Létrehoz egy típustK
tulajdonsághalmazzal, ahol minden tulajdonságT
típusú.Exclude<T, U>
: Kizárja aT
-ből az összes olyan típust, amely hozzárendelhető azU
-hoz.Extract<T, U>
: Kinyeri aT
-ből az összes olyan típust, amely hozzárendelhető azU
-hoz.NonNullable<T>
: Kizárja anull
ésundefined
értékeket aT
-ből.Parameters<T>
: Lekéri egyT
függvénytípus paramétereit.ReturnType<T>
: Lekéri egyT
függvénytípus visszatérési típusát.InstanceType<T>
: Lekéri egyT
konstruktorfüggvény-típus példánytípusát.
Ezek a segédtípusok hatékony eszközök, amelyek leegyszerűsíthetik az összetett típusmanipulációkat. Például a Pick
és a Partial
kombinálásával létrehozhat egy típust, amely csak bizonyos tulajdonságokat tesz opcionálissá:
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">;
Ebben a példában az OptionalDescriptionProduct
a Product
minden tulajdonságával rendelkezik, de a description
tulajdonság opcionális.
Template Literal típusok használata
A Template Literal típusok lehetővé teszik string literálok alapján történő típusok létrehozását. Mapped és Conditional típusokkal kombinálva dinamikus és kifejező típus-transzformációkat hozhatunk létre velük. Például létrehozhat egy típust, amely minden tulajdonságnevet egy adott stringgel lát el előtagként:
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_">;
Ebben a példában a PrefixedSettings
-nek data_apiUrl
és data_timeout
tulajdonságai lesznek.
Jó gyakorlatok és megfontolások
- Maradj egyszerű: Bár a Mapped és Conditional típusok hatékonyak, bonyolultabbá is tehetik a kódot. Törekedj arra, hogy a típus-transzformációid a lehető legegyszerűbbek legyenek.
- Használj segédtípusokat: Amikor csak lehetséges, használd a TypeScript beépített segédtípusait. Ezek jól teszteltek és egyszerűsíthetik a kódodat.
- Dokumentáld a típusaidat: Dokumentáld egyértelműen a típus-transzformációidat, különösen, ha összetettek. Ez segít más fejlesztőknek megérteni a kódodat.
- Teszeld a típusaidat: Használd a TypeScript típusellenőrzőjét annak biztosítására, hogy a típus-transzformációid az elvárt módon működnek. Írhatsz unit teszteket a típusaid viselkedésének ellenőrzésére.
- Vedd figyelembe a teljesítményt: Az összetett típus-transzformációk befolyásolhatják a TypeScript fordító teljesítményét. Légy tekintettel a típusaid bonyolultságára, és kerüld a felesleges számításokat.
Összegzés
A Mapped Types és a Conditional Types a TypeScript hatékony funkciói, amelyek lehetővé teszik rendkívül rugalmas és kifejező típus-transzformációk létrehozását. Ezen koncepciók elsajátításával javíthatja TypeScript alkalmazásai típusbiztonságát, karbantarthatóságát és általános minőségét. Az egyszerű átalakításoktól, mint a tulajdonságok opcionálissá vagy írásvédetté tétele, a bonyolult rekurzív transzformációkig és feltételes logikáig, ezek a funkciók biztosítják azokat az eszközöket, amelyekre a robusztus és skálázható alkalmazások építéséhez szüksége van. Folytassa a felfedezést és a kísérletezést ezekkel a funkciókkal, hogy kiaknázza teljes potenciáljukat, és még képzettebb TypeScript fejlesztővé váljon.
Ahogy folytatja TypeScript-es utazását, ne felejtse el kihasználni a rendelkezésre álló rengeteg erőforrást, beleértve a hivatalos TypeScript dokumentációt, az online közösségeket és a nyílt forráskódú projekteket. Fogadja el a Mapped és Conditional típusok erejét, és jól felkészült lesz arra, hogy megbirkózzon még a legnehezebb típusokkal kapcsolatos problémákkal is.