Hyödynnä TypeScriptin teho edistyneillä ehdollisilla ja muunnelluilla tyypeillä. Opi luomaan joustavia, tyyppiturvallisia sovelluksia, jotka mukautuvat monimutkaisiin tietorakenteisiin.
TypeScriptin edistyneet mallit: Ehdollisten ja muunneltujen tyyppien hallinta
TypeScriptin voima piilee sen kyvyssä tarjota vahva tyypitys, jonka avulla voit havaita virheet aikaisin ja kirjoittaa ylläpidettävämpää koodia. Vaikka perustyypit, kuten string
, number
ja boolean
, ovat perustavanlaatuisia, TypeScriptin edistyneet ominaisuudet, kuten ehdolliset ja muunnellut tyypit, avaavat uuden ulottuvuuden joustavuudelle ja tyyppiturvallisuudelle. Tämä kattava opas syventyy näihin tehokkaisiin käsitteisiin ja antaa sinulle tiedot luoda todella dynaamisia ja mukautuvia TypeScript-sovelluksia.
Mitä ovat ehdolliset tyypit?
Ehdolliset tyypit mahdollistavat tyyppien määrittelyn, jotka riippuvat ehdosta, samankaltaisesti kuin JavaScriptin ternäärioperaattori (ehto ? tosiArvo : epätosiArvo
). Niiden avulla voit ilmaista monimutkaisia tyyppisuhteita sen perusteella, täyttääkö tyyppi tietyn rajoitteen.
Syntaksi
Ehdollisen tyypin perussyntaksi on:
T extends U ? X : Y
T
: Tarkistettava tyyppi.U
: Tyyppi, johon verrataan.extends
: Avainsana, joka osoittaa alityyppisuhteen.X
: Tyyppi, jota käytetään, josT
on sijoitettavissaU
:hun.Y
: Tyyppi, jota käytetään, josT
ei ole sijoitettavissaU
:hun.
Yksinkertaisesti sanottuna, jos T extends U
evaluoituu todeksi, tyyppi ratkeaa X
:ksi; muussa tapauksessa se ratkeaa Y
:ksi.
Käytännön esimerkkejä
1. Funktion parametrin tyypin määrittäminen
Oletetaan, että haluat luoda tyypin, joka määrittää, onko funktion parametri merkkijono vai numero:
type ParamType<T> = T extends string ? string : number;
function processValue(value: ParamType<string | number>): void {
if (typeof value === "string") {
console.log("Arvo on merkkijono:", value);
} else {
console.log("Arvo on numero:", value);
}
}
processValue("hello"); // Tuloste: Arvo on merkkijono: hello
processValue(123); // Tuloste: Arvo on numero: 123
Tässä esimerkissä ParamType<T>
on ehdollinen tyyppi. Jos T
on merkkijono, tyyppi ratkeaa string
-tyypiksi; muuten se ratkeaa number
-tyypiksi. Funktio processValue
hyväksyy joko merkkijonon tai numeron tämän ehdollisen tyypin perusteella.
2. Palautustyypin päätteleminen syötetyypin perusteella
Kuvittele tilanne, jossa sinulla on funktio, joka palauttaa eri tyyppejä syötteen perusteella. Ehdolliset tyypit voivat auttaa sinua määrittämään oikean palautustyypin:
interface StringProcessor {
process(input: string): number;
}
interface NumberProcessor {
process(input: number): string;
}
type Processor<T> = T extends string ? StringProcessor : NumberProcessor;
function createProcessor<T extends string | number>(input: T): Processor<T> {
if (typeof input === "string") {
return { process: (input: string) => input.length } as Processor<T>;
} else {
return { process: (input: number) => input.toString() } as Processor<T>;
}
}
const stringProcessor = createProcessor("example");
const numberProcessor = createProcessor(42);
console.log(stringProcessor.process("example")); // Tuloste: 7
console.log(numberProcessor.process(42)); // Tuloste: "42"
Tässä Processor<T>
-tyyppi valitsee ehdollisesti joko StringProcessor
- tai NumberProcessor
-tyypin syötteen tyypin perusteella. Tämä varmistaa, että createProcessor
-funktio palauttaa oikean tyyppisen prosessoriobjektin.
3. Erotellut uniot (Discriminated Unions)
Ehdolliset tyypit ovat erittäin tehokkaita työskenneltäessä eroteltujen unioiden kanssa. Eroteltu unio on uniotyyppi, jossa jokaisella jäsenellä on yhteinen, yksilöllisen tyypin ominaisuus (erottelija). Tämän avulla voit rajata tyyppiä kyseisen ominaisuuden arvon perusteella.
interface Square {
kind: "square";
size: number;
}
interface Circle {
kind: "circle";
radius: number;
}
type Shape = Square | Circle;
type Area<T extends Shape> = T extends { kind: "square" } ? number : string;
function calculateArea(shape: Shape): Area<typeof shape> {
if (shape.kind === "square") {
return shape.size * shape.size;
} else {
return Math.PI * shape.radius * shape.radius;
}
}
const mySquare: Square = { kind: "square", size: 5 };
const myCircle: Circle = { kind: "circle", radius: 3 };
console.log(calculateArea(mySquare)); // Tuloste: 25
console.log(calculateArea(myCircle)); // Tuloste: 28.274333882308138
Tässä esimerkissä Shape
-tyyppi on eroteltu unio. Area<T>
-tyyppi käyttää ehdollista tyyppiä määrittääkseen, onko muoto neliö vai ympyrä, ja määrittää palautustyypiksi number
neliöille ja string
ympyröille (vaikka todellisessa tilanteessa haluaisit todennäköisesti johdonmukaiset palautustyypit, tämä demonstroi periaatetta).
Tärkeimmät opit ehdollisista tyypeistä
- Mahdollistavat tyyppien määrittelyn ehtojen perusteella.
- Parantavat tyyppiturvallisuutta ilmaisemalla monimutkaisia tyyppisuhteita.
- Ovat hyödyllisiä työskenneltäessä funktion parametrien, palautustyyppien ja eroteltujen unioiden kanssa.
Mitä ovat muunnellut tyypit (Mapped Types)?
Muunnellut tyypit tarjoavat tavan muuntaa olemassa olevia tyyppejä iteroimalla niiden ominaisuuksien yli. Niiden avulla voit luoda uusia tyyppejä toisen tyypin ominaisuuksien perusteella ja soveltaa muutoksia, kuten tehdä ominaisuuksista valinnaisia, vain luku -muotoisia tai muuttaa niiden tyyppejä.
Syntaksi
Muunnellun tyypin yleinen syntaksi on:
type NewType<T> = {
[K in keyof T]: ModifiedType;
};
T
: Lähtötyyppi.keyof T
: Tyyppioperaattori, joka palauttaa unionin kaikistaT
:n ominaisuusavaimista.K in keyof T
: Iteroi jokaisen avaimen ylikeyof T
:ssä ja sijoittaa kunkin avaimen tyyppimuuttujaanK
.ModifiedType
: Tyyppi, johon kukin ominaisuus muunnetaan. Tämä voi sisältää ehdollisia tyyppejä tai muita tyyppimuunnoksia.
Käytännön esimerkkejä
1. Ominaisuuksien tekeminen valinnaisiksi
Voit käyttää muunneltua tyyppiä tehdäkseksi kaikki olemassa olevan tyypin ominaisuudet valinnaisiksi:
interface User {
id: number;
name: string;
email: string;
}
type PartialUser = {
[K in keyof User]?: User[K];
};
const partialUser: PartialUser = {
name: "John Doe",
}; // Kelvollinen, koska 'id' ja 'email' ovat valinnaisia
Tässä PartialUser
on muunneltu tyyppi, joka iteroi User
-rajapinnan avainten yli. Jokaiselle avaimelle K
se tekee ominaisuudesta valinnaisen lisäämällä ?
-muuntimen. User[K]
hakee ominaisuuden K
tyypin User
-rajapinnasta.
2. Ominaisuuksien tekeminen vain luku -muotoisiksi
Vastaavasti voit tehdä kaikki olemassa olevan tyypin ominaisuudet vain luku -muotoisiksi:
interface Product {
id: number;
name: string;
price: number;
}
type ReadonlyProduct = {
readonly [K in keyof Product]: Product[K];
};
const readonlyProduct: ReadonlyProduct = {
id: 123,
name: "Example Product",
price: 25.00,
};
// readonlyProduct.price = 30.00; // Virhe: 'price'-ominaisuuteen ei voi sijoittaa arvoa, koska se on vain luku -ominaisuus.
Tässä tapauksessa ReadonlyProduct
on muunneltu tyyppi, joka lisää readonly
-muuntimen jokaiseen Product
-rajapinnan ominaisuuteen.
3. Ominaisuustyyppien muuntaminen
Muunneltuja tyyppejä voidaan myös käyttää ominaisuuksien tyyppien muuntamiseen. Voit esimerkiksi luoda tyypin, joka muuntaa kaikki merkkijono-ominaisuudet numeroiksi:
interface Config {
apiUrl: string;
timeout: string;
maxRetries: number;
}
type NumericConfig = {
[K in keyof Config]: Config[K] extends string ? number : Config[K];
};
const numericConfig: NumericConfig = {
apiUrl: 123, // Pitää olla numero muunnoksen vuoksi
timeout: 456, // Pitää olla numero muunnoksen vuoksi
maxRetries: 3,
};
Tämä esimerkki demonstroi ehdollisen tyypin käyttöä muunnellun tyypin sisällä. Jokaiselle ominaisuudelle K
se tarkistaa, onko Config[K]
:n tyyppi merkkijono. Jos on, tyyppi muunnetaan number
-tyypiksi; muussa tapauksessa se pysyy muuttumattomana.
4. Avainten uudelleenkartoitus (TypeScript 4.1:stä lähtien)
TypeScript 4.1 esitteli mahdollisuuden uudelleenkartoittaa avaimia muunneltujen tyyppien sisällä käyttämällä as
-avainsanaa. Tämä mahdollistaa uusien tyyppien luomisen eri ominaisuusnimillä alkuperäisen tyypin perusteella.
interface Event {
eventId: string;
eventName: string;
eventDate: Date;
}
type TransformedEvent = {
[K in keyof Event as `new${Capitalize<string&K>}`]: Event[K];
};
// Tulos:
// {
// newEventId: string;
// newEventName: string;
// newEventDate: Date;
// }
//Capitalize-funktiota käytetään ison alkukirjaimen luomiseen
type Capitalize<S extends string> = Uppercase<string&S> extends never ? string : `$Capitalize<S>`;
//Käyttö todellisen objektin kanssa
const myEvent: TransformedEvent = {
newEventId: "123",
newEventName: "New Name",
newEventDate: new Date()
};
Tässä TransformedEvent
-tyyppi uudelleenkartoittaa jokaisen avaimen K
uudeksi avaimeksi, jonka etuliitteenä on "new" ja joka on kirjoitettu isolla alkukirjaimella. Capitalize
-apufunktio varmistaa, että avaimen ensimmäinen kirjain on iso. Leikkaustyyppi string & K
varmistaa, että käsittelemme vain merkkijonoavaimia ja saamme oikean literaalityypin K:sta. Avainten uudelleenkartoitus avaa tehokkaita mahdollisuuksia tyyppien muuntamiseen ja mukauttamiseen erityistarpeisiin. Tämä mahdollistaa avainten nimeämisen uudelleen, suodattamisen tai muokkaamisen monimutkaisen logiikan perusteella.
Tärkeimmät opit muunnelluista tyypeistä
- Mahdollistavat olemassa olevien tyyppien muuntamisen iteroimalla niiden ominaisuuksien yli.
- Sallivat ominaisuuksien tekemisen valinnaisiksi, vain luku -muotoisiksi tai niiden tyyppien muuttamisen.
- Ovat hyödyllisiä uusien tyyppien luomisessa toisen tyypin ominaisuuksien perusteella.
- Avainten uudelleenkartoitus (esitelty TypeScript 4.1:ssä) tarjoaa entistä enemmän joustavuutta tyyppimuunnoksissa.
Ehdollisten ja muunneltujen tyyppien yhdistäminen
Ehdollisten ja muunneltujen tyyppien todellinen voima tulee esiin, kun niitä yhdistetään. Tämä mahdollistaa erittäin joustavien ja ilmaisuvoimaisten tyyppimäärittelyjen luomisen, jotka voivat mukautua monenlaisiin skenaarioihin.
Esimerkki: Ominaisuuksien suodattaminen tyypin perusteella
Oletetaan, että haluat luoda tyypin, joka suodattaa objektin ominaisuuksia niiden tyypin perusteella. Haluat esimerkiksi ehkä poimia vain merkkijono-ominaisuudet objektista.
interface Data {
name: string;
age: number;
city: string;
country: string;
isEmployed: boolean;
}
type StringProperties<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
type StringData = StringProperties<Data>;
// Tulos:
// {
// name: string;
// city: string;
// country: string;
// }
const stringData: StringData = {
name: "John",
city: "New York",
country: "USA",
};
Tässä esimerkissä StringProperties<T>
-tyyppi käyttää muunneltua tyyppiä, jossa on avainten uudelleenkartoitus ja ehdollinen tyyppi. Jokaiselle ominaisuudelle K
se tarkistaa, onko T[K]
:n tyyppi merkkijono. Jos on, avain säilytetään; muuten se kartoitetaan never
-tyypiksi, mikä käytännössä suodattaa sen pois. never
muunnellun tyypin avaimena poistaa sen tuloksena syntyvästä tyypistä. Tämä varmistaa, että vain merkkijono-ominaisuudet sisällytetään StringData
-tyyppiin.
TypeScriptin aputyypit (Utility Types)
TypeScript tarjoaa useita sisäänrakennettuja aputyyppejä, jotka hyödyntävät ehdollisia ja muunneltuja tyyppejä yleisten tyyppimuunnosten suorittamiseen. Näiden aputyyppien ymmärtäminen voi merkittävästi yksinkertaistaa koodiasi ja parantaa tyyppiturvallisuutta.
Yleisimmät aputyypit
Partial<T>
: Tekee kaikistaT
:n ominaisuuksista valinnaisia.Readonly<T>
: Tekee kaikistaT
:n ominaisuuksista vain luku -muotoisia.Required<T>
: Tekee kaikistaT
:n ominaisuuksista pakollisia (poistaa?
-muuntimen).Pick<T, K extends keyof T>
: Valitsee joukon ominaisuuksiaK
tyypistäT
.Omit<T, K extends keyof T>
: Poistaa joukon ominaisuuksiaK
tyypistäT
.Record<K extends keyof any, T>
: Rakentaa tyypin, jolla on joukko ominaisuuksiaK
, joiden tyyppi onT
.Exclude<T, U>
: Sulkee poisT
:stä kaikki tyypit, jotka ovat sijoitettavissaU
:hun.Extract<T, U>
: PoimiiT
:stä kaikki tyypit, jotka ovat sijoitettavissaU
:hun.NonNullable<T>
: Sulkee poisnull
- jaundefined
-arvotT
:stä.Parameters<T>
: Hakee funktion tyypinT
parametrit monikkona (tuple).ReturnType<T>
: Hakee funktion tyypinT
palautustyypin.InstanceType<T>
: Hakee konstruktorifunktion tyypinT
instanssityypin.ThisType<T>
: Toimii merkkinä kontekstuaalisellethis
-tyypille.
Nämä aputyypit on rakennettu käyttämällä ehdollisia ja muunneltuja tyyppejä, mikä osoittaa näiden edistyneiden TypeScript-ominaisuuksien tehon ja joustavuuden. Esimerkiksi Partial<T>
on määritelty seuraavasti:
type Partial<T> = {
[P in keyof T]?: T[P];
};
Parhaat käytännöt ehdollisten ja muunneltujen tyyppien käyttöön
Vaikka ehdolliset ja muunnellut tyypit ovat tehokkaita, ne voivat myös tehdä koodistasi monimutkaisempaa, jos niitä ei käytetä huolellisesti. Tässä on joitakin parhaita käytäntöjä, jotka kannattaa pitää mielessä:
- Pidä se yksinkertaisena: Vältä liian monimutkaisia ehdollisia ja muunneltuja tyyppejä. Jos tyyppimäärittelystä tulee liian sekava, harkitse sen jakamista pienempiin, hallittavampiin osiin.
- Käytä kuvaavia nimiä: Anna ehdollisille ja muunnelluille tyypeillesi kuvaavia nimiä, jotka kertovat selvästi niiden tarkoituksen.
- Dokumentoi tyyppisi: Lisää kommentteja selittämään ehdollisten ja muunneltujen tyyppiesi logiikkaa, varsinkin jos ne ovat monimutkaisia.
- Hyödynnä aputyyppejä: Ennen kuin luot oman ehdollisen tai muunnellun tyypin, tarkista, voiko sisäänrakennettu aputyyppi saavuttaa saman tuloksen.
- Testaa tyyppisi: Varmista, että ehdolliset ja muunnellut tyyppisi toimivat odotetusti kirjoittamalla yksikkötestejä, jotka kattavat eri skenaarioita.
- Ota huomioon suorituskyky: Monimutkaiset tyyppilaskennat voivat vaikuttaa kääntämisaikoihin. Ole tietoinen tyyppimäärittelyjesi suorituskykyvaikutuksista.
Yhteenveto
Ehdolliset ja muunnellut tyypit ovat olennaisia työkaluja TypeScriptin hallitsemiseksi. Ne mahdollistavat erittäin joustavien, tyyppiturvallisten ja ylläpidettävien sovellusten luomisen, jotka mukautuvat monimutkaisiin tietorakenteisiin ja dynaamisiin vaatimuksiin. Ymmärtämällä ja soveltamalla tässä oppaassa käsiteltyjä käsitteitä voit hyödyntää TypeScriptin koko potentiaalin ja kirjoittaa vankempaa ja skaalautuvampaa koodia. Kun jatkat TypeScriptin tutkimista, muista kokeilla erilaisia ehdollisten ja muunneltujen tyyppien yhdistelmiä löytääksesi uusia tapoja ratkaista haastavia tyypitysongelmia. Mahdollisuudet ovat todella rajattomat.