Tutki TypeScriptin edistyneitä ominaisuuksia, kuten template literal- ja conditional-tyyppejä, kirjoittaaksesi ilmeikkäämpää ja ylläpidettävämpää koodia. Hallitse tyyppimanipulointi monimutkaisissa skenaarioissa.
TypeScriptin edistyneet tyypit: Hallitse Template Literal- ja Conditional-tyypit
TypeScriptin vahvuus piilee sen tehokkaassa tyyppijärjestelmässä. Vaikka perus tyypit kuten string, number ja boolean riittävät monissa skenaarioissa, edistyneet ominaisuudet kuten template literal-tyypit ja conditional-tyypit avaavat uuden tason ilmeikkyydelle ja tyyppiturvallisuudelle. Tämä opas tarjoaa kattavan yleiskatsauksen näistä edistyneistä tyypeistä, tutkien niiden kykyjä ja esitellen käytännön sovelluksia.
Template Literal -tyyppien ymmärtäminen
Template literal -tyypit rakentuvat JavaScriptin template literaleille, jolloin voit määrittää tyyppejä merkkijonojen interpolaation perusteella. Tämä mahdollistaa tyyppien luomisen, jotka edustavat tiettyjä merkkijonomalleja, tehden koodistasi robustimman ja ennustettavamman.
Perussyntaksi ja käyttö
Template literal -tyypit käyttävät takapraameja (`) sulkemaan tyyppimääritelmän, samalla tavoin kuin JavaScriptin template literalit. Takapraamien sisällä voit interpoloida muita tyyppejä käyttäen ${} -syntaksia. Tässä tapahtuu taika – olet pohjimmiltaan luomassa tyypin, joka on merkkijono, joka on rakennettu kääntämisajassa interpoloitujen tyyppien perusteella.
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIEndpoint = `/api/${string}`;
// Esimerkkikäyttö
const getEndpoint: APIEndpoint = "/api/users"; // Kelpaa
const postEndpoint: APIEndpoint = "/api/products/123"; // Kelpaa
const invalidEndpoint: APIEndpoint = "/admin/settings"; // TypeScript ei näytä virhettä tässä, koska `string` voi olla mitä tahansa
Tässä esimerkissä APIEndpoint on tyyppi, joka edustaa mitä tahansa merkkijonoa, joka alkaa /api/. Vaikka tämä perustason esimerkki on hyödyllinen, template literal -tyyppien todellinen voima ilmenee, kun ne yhdistetään tarkempiin tyyppirajoituksiin.
Yhdistäminen union-tyyppeihin
Template literal -tyypit loistavat todella, kun niitä käytetään union-tyyppien kanssa. Tämän avulla voit luoda tyyppejä, jotka edustavat tiettyä joukkoa merkkijonoyhdistelmiä.
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIPath = "users" | "products" | "orders";
type APIEndpoint = `/${APIPath}/${HTTPMethod}`;
// Kelvolliset API-päätteet
const getUsers: APIEndpoint = "/users/GET";
const postProducts: APIEndpoint = "/products/POST";
// Virheelliset API-päätteet (johtaa TypeScript-virheisiin)
// const invalidEndpoint: APIEndpoint = "/users/PATCH"; // Virhe: "/users/PATCH" ei ole määritettävissä tyypiksi "/users/GET" | "/users/POST" | ...
Nyt APIEndpoint on rajoittavampi tyyppi, joka sallii vain tietyt API-polkujen ja HTTP-metodien yhdistelmät. TypeScript merkitsee kaikki yritykset käyttää virheellisiä yhdistelmiä, mikä parantaa tyyppiturvallisuutta.
Merkkijonojen käsittely template literal -tyypeillä
TypeScript tarjoaa sisäisiä merkkijonojen käsittelytyyppejä, jotka toimivat saumattomasti template literal -tyyppien kanssa. Nämä tyypit mahdollistavat merkkijonojen muuntamisen kääntämisajassa.
- Uppercase: Muuntaa merkkijonon isoiksi kirjaimiksi.
- Lowercase: Muuntaa merkkijonon pieniksi kirjaimiksi.
- Capitalize: Isoaa merkkijonon ensimmäisen kirjaimen.
- Uncapitalize: Pienentää merkkijonon ensimmäisen kirjaimen.
type Greeting = "hello world";
type UppercaseGreeting = Uppercase; // "HELLO WORLD"
type LowercaseGreeting = Lowercase; // "hello world"
type CapitalizedGreeting = Capitalize; // "Hello world"
type UncapitalizedGreeting = Uncapitalize; // "hello world"
Nämä merkkijonojen käsittelytyypit ovat erityisen hyödyllisiä luotaessa automaattisesti tyyppejä nimeämiskäytäntöjen perusteella. Voit esimerkiksi johtaa toimintotyypit tapahtumien nimistä tai päinvastoin.
Template literal -tyyppien käytännön sovellukset
- API-päätepisteen määrittely: Kuten edellä esitetty, määritellään API-päätepisteet tarkkoilla tyyppirajoituksilla.
- Tapahtumien käsittely: Luodaan tyyppejä tapahtumien nimille, joilla on tietyt etuliitteet ja jälkiliitteet.
- CSS-luokan generointi: Generoidaan CSS-luokkien nimiä komponenttien nimien ja tilojen perusteella.
- Tietokantakyselyn rakentaminen: Varmistetaan tyyppiturvallisuus tietokantakyselyjä rakennettaessa.
Kansainvälinen esimerkki: Valuutan muotoilu
Kuvittele rakentavasi taloussovellusta, joka tukee useita valuuttoja. Voit käyttää template literal -tyyppejä pakottaaksesi oikean valuutan muotoilun.
type CurrencyCode = "USD" | "EUR" | "GBP" | "JPY";
type CurrencyFormat = `${number} ${T}`;
const priceUSD: CurrencyFormat<"USD"> = "100 USD"; // Kelpaa
const priceEUR: CurrencyFormat<"EUR"> = "50 EUR"; // Kelpaa
// const priceInvalid: CurrencyFormat<"USD"> = "100 EUR"; // Virhe: Type 'string' is not assignable to type '`${number} USD`'.
function formatCurrency(amount: number, currency: T): CurrencyFormat {
return `${amount} ${currency}`;
}
const formattedUSD = formatCurrency(250, "USD"); // Type: "250 USD"
const formattedEUR = formatCurrency(100, "EUR"); // Type: "100 EUR"
Tämä esimerkki varmistaa, että valuutan arvot muotoillaan aina oikealla valuuttakoodilla, mikä estää mahdollisia virheitä.
Conditional-tyyppeihin syventyminen
Conditional-tyypit tuovat haarautumislogiikkaa TypeScriptin tyyppijärjestelmään, jolloin voit määrittää tyyppejä, jotka riippuvat muista tyypeistä. Tämä ominaisuus on uskomattoman tehokas luotaessa erittäin joustavia ja uudelleenkäytettäviä tyyppimääritelmiä.
Perussyntaksi ja käyttö
Conditional-tyypit käyttävät infer-avainsanaa ja ternäärioperaattoria (condition ? trueType : falseType) tyyppiehtojen määrittämiseen.
type IsString = T extends string ? true : false;
type StringCheck = IsString; // type StringCheck = true
type NumberCheck = IsString; // type NumberCheck = false
Tässä esimerkissä IsString on conditional-tyyppi, joka tarkistaa, onko T määritettävissä string-tyyppiseksi. Jos on, tyyppi ratkeaa true; muuten se ratkeaa false.
infer-avainsana
infer-avainsanan avulla voit erottaa tyypin tyypistä. Tämä on erityisen hyödyllistä työskennellessäsi monimutkaisten tyyppien, kuten funktiotyyppien tai taulukkotyyppien, kanssa.
type ReturnType any> = T extends (...args: any) => infer R ? R : any;
function add(a: number, b: number): number {
return a + b;
}
type AddReturnType = ReturnType; // type AddReturnType = number
Tässä esimerkissä ReturnType erottaa funktion paluutyypin tyypistä T. infer R -osa conditional-tyypistä päättelee paluutyypin ja määrittää sen tyyppimuuttujalle R. Jos T ei ole funktion tyyppi, tyyppi ratkeaa any.
Distributive Conditional-tyypit
Conditional-tyypit muuttuvat distributiivisiksi, kun tarkistettu tyyppi on alaston tyyppiparametri. Tämä tarkoittaa, että conditional-tyyppiä sovelletaan union-tyypin jokaiseen jäseneen erikseen.
type ToArray = T extends any ? T[] : never;
type NumberOrStringArray = ToArray; // type NumberOrStringArray = string[] | number[]
Tässä esimerkissä ToArray muuntaa tyypin T taulukkotyypiksi. Koska T on alaston tyyppiparametri (ei ole kääritty toiseen tyyppiin), conditional-tyyppiä sovelletaan erikseen number ja string, mikä johtaa number[] ja string[] union-tyyppiin.
Conditional-tyyppien käytännön sovellukset
- Paluutyyppien erottaminen: Kuten edellä esitetty, erottaa funktion paluutyypin.
- Tyyppien suodattaminen unionista: Luodaan tyyppi, joka sisältää vain tietyt tyypit unionista.
- Ylikuormitettujen funktiotyyppien määrittäminen: Luodaan erilaisia funktiotyyppejä syöttötyyppien perusteella.
- Tyyppivahdit: Määritellään funktioita, jotka kaventavat muuttujan tyyppiä.
Kansainvälinen esimerkki: Eri päivämäärämuotojen käsittely
Maailman eri alueilla käytetään eri päivämäärämuotoja. Voit käyttää conditional-tyyppejä näiden vaihteluiden käsittelyyn.
type DateFormat = "YYYY-MM-DD" | "MM/DD/YYYY" | "DD.MM.YYYY";
type ParseDate = T extends "YYYY-MM-DD"
? { year: number; month: number; day: number; format: "YYYY-MM-DD" }
: T extends "MM/DD/YYYY"
? { month: number; day: number; year: number; format: "MM/DD/YYYY" }
: T extends "DD.MM.YYYY"
? { day: number; month: number; year: number; format: "DD.MM.YYYY" }
: never;
function parseDate(dateString: string, format: T): ParseDate {
// (Toteutus hoitaisi eri päivämäärämuotoja)
if (format === "YYYY-MM-DD") {
const [year, month, day] = dateString.split("-").map(Number);
return { year, month, day, format } as ParseDate;
} else if (format === "MM/DD/YYYY") {
const [month, day, year] = dateString.split("/").map(Number);
return { month, day, year, format } as ParseDate;
} else if (format === "DD.MM.YYYY") {
const [day, month, year] = dateString.split(".").map(Number);
return { day, month, year, format } as ParseDate;
} else {
throw new Error("Virheellinen päivämäärämuoto");
}
}
const parsedDateISO = parseDate("2023-10-27", "YYYY-MM-DD"); // Type: { year: number; month: number; day: number; format: "YYYY-MM-DD"; }
const parsedDateUS = parseDate("10/27/2023", "MM/DD/YYYY"); // Type: { month: number; day: number; year: number; format: "MM/DD/YYYY"; }
const parsedDateEU = parseDate("27.10.2023", "DD.MM.YYYY"); // Type: { day: number; month: number; year: number; format: "DD.MM.YYYY"; }
console.log(parsedDateISO.year); // Käytä vuotta tietäen, että se on olemassa
Tämä esimerkki käyttää conditional-tyyppejä määritelläkseen erilaisia päivämäärän jäsentämisfunktioita määritetyn päivämäärämuodon perusteella. ParseDate-tyyppi varmistaa, että palautetulla objektilla on oikeat ominaisuudet muodon perusteella.
Template Literal- ja Conditional-tyyppien yhdistäminen
Todellinen voima syntyy, kun yhdistät template literal -tyypit ja conditional-tyypit. Tämä mahdollistaa uskomattoman tehokkaan tyyppimanipuloinnin.
type EventName = `on${Capitalize}`;
type ExtractEventPayload = T extends EventName
? { type: T; payload: any } // Yksinkertaistettu esittelyä varten
: never;
type ClickEvent = EventName<"click">; // "onClick"
type MouseOverEvent = EventName<"mouseOver">; // "onMouseOver"
//Esimerkkifunktio, joka ottaa tyypin
function processEvent(event: T): ExtractEventPayload {
//Todellisessa toteutuksessa me todella lähettäisimme tapahtuman.
console.log(`Käsitellään tapahtumaa ${event}`);
//Todellisessa toteutuksessa, hyötykuorma perustuisi tapahtuman tyyppiin.
return { type: event, payload: {} } as ExtractEventPayload;
}
//Huomaa, että palautustyypit ovat hyvin tarkkoja:
const clickEvent = processEvent("onClick"); // { type: "onClick"; payload: any; }
const mouseOverEvent = processEvent("onMouseOver"); // { type: "onMouseOver"; payload: any; }
//Jos käytät muita merkkijonoja, saat never:
// const someOtherEvent = processEvent("someOtherEvent"); // Tyyppi on `never`
Parhaat käytännöt ja huomioitavaa
- Pidä se yksinkertaisena: Vaikka nämä edistyneet tyypit ovat tehokkaita, ne voivat muuttua nopeasti monimutkaisiksi. Pyri selkeyteen ja ylläpidettävyyteen.
- Testaa perusteellisesti: Varmista, että tyyppimääritykset toimivat odotetusti kirjoittamalla kattavat yksikkötestit.
- Dokumentoi koodisi: Dokumentoi selkeästi edistyneiden tyyppien tarkoitus ja toiminta koodin luettavuuden parantamiseksi.
- Harkitse suorituskykyä: Liiallinen edistyneiden tyyppien käyttö voi vaikuttaa kääntämisaikaan. Profiloi koodisi ja optimoi tarvittaessa.
Johtopäätös
Template literal -tyypit ja conditional-tyypit ovat tehokkaita työkaluja TypeScriptin arsenaalissa. Hallitsemalla nämä edistyneet tyypit voit kirjoittaa ilmeikkäämpää, ylläpidettävämpää ja tyyppiturvallisempaa koodia. Nämä ominaisuudet mahdollistavat monimutkaisten suhteiden tallentamisen tyyppien välillä, tiukempien rajoitusten asettamisen ja erittäin uudelleenkäytettävien tyyppimääritysten luomisen. Ota nämä tekniikat käyttöön parantaaksesi TypeScript-taitojasi ja rakentaaksesi robusteja ja skaalautuvia sovelluksia globaalille yleisölle.