Minge kaugemale põhitüüpimisest. Omandage TypeScripti täiustatud funktsioonid, nagu tingimuslikud tüübid ja sõnetöötlus, et luua uskumatult töökindlaid ja tüübikindlaid API-sid. Põhjalik juhend globaalsetele arendajatele.
TypeScripti täieliku potentsiaali avamine: süvitsiminek tingimuslikesse tüüpidesse, malliliteraalidesse ja täiustatud sõnetöötlusesse
Kaasaegse tarkvaraarenduse maailmas on TypeScript arenenud palju kaugemale oma esialgsest rollist lihtsa JavaScripti tüübikontrollijana. Sellest on saanud keerukas tööriist, mida võib kirjeldada kui tüübitaseme programmeerimist. See paradigma võimaldab arendajatel kirjutada koodi, mis opereerib tüüpide endiga, luues dünaamilisi, isedokumenteeruvaid ja märkimisväärselt turvalisi API-sid. Selle revolutsiooni keskmes on kolm võimsat funktsiooni, mis töötavad koos: tingimuslikud tüübid (Conditional Types), malliliteraalide tüübid (Template Literal Types) ja sisseehitatud sõnetöötluse tüüpide komplekt.
Arendajatele üle maailma, kes soovivad oma TypeScripti oskusi täiendada, ei ole nende kontseptsioonide mõistmine enam luksus – see on hädavajalik skaleeritavate ja hooldatavate rakenduste loomiseks. See juhend viib teid süvitsi, alustades aluspõhimõtetest ja jõudes keerukate, reaalsete mustriteni, mis demonstreerivad nende kombineeritud jõudu. Olenemata sellest, kas loote disainisüsteemi, tüübikindlat API-klienti või keerukat andmetöötlusraamistikku, muudab nende funktsioonide valdamine põhjalikult seda, kuidas te TypeScripti kirjutate.
Alus: tingimuslikud tüübid (The `extends` kolmikoperaator)
Oma olemuselt võimaldab tingimuslik tüüp valida kahe võimaliku tüübi vahel, tuginedes tüübisuhte kontrollile. Kui olete tuttav JavaScripti kolmikoperaatoriga (condition ? valueIfTrue : valueIfFalse), leiate süntaksi kohe intuitiivseks:
type Result = SomeType extends OtherType ? TrueType : FalseType;
Siin toimib võtmesõna extends meie tingimusena. See kontrollib, kas SomeType on omistatav tüübile OtherType. Vaatame seda lähemalt lihtsa näite abil.
Põhinäide: tüübi kontrollimine
Kujutage ette, et tahame luua tüübi, mis laheneb väärtuseks true, kui antud tüüp T on sõne, ja vastasel juhul false.
type IsString
Seejärel saame seda tüüpi kasutada nii:
type A = IsString<"hello">; // tüüp A on true
type B = IsString<123>; // tüüp B on false
See on fundamentaalne ehituskivi. Kuid tingimuslike tüüpide tõeline jõud avaldub siis, kui seda kombineerida võtmesõnaga infer.
Võtmesõna `infer` jõud: tüüpide eraldamine seestpoolt
Võtmesõna infer on mängumuutja. See võimaldab deklareerida uue geneerilise tüübimuutuja sees extends klauslis, püüdes tõhusalt kinni osa tüübist, mida kontrollite. Mõelge sellest kui tüübitaseme muutujate deklareerimisest, mis saab oma väärtuse mustrisobitusest.
Klassikaline näide on Promise'i sees oleva tüübi lahtipakkimine.
type UnwrapPromise
Analüüsime seda:
T extends Promise: See kontrollib, kasTonPromise. Kui on, üritab TypeScript struktuuri sobitada.infer U: Kui sobitamine õnnestub, püüab TypeScript kinni tüübi, milleksPromiselaheneb, ja paigutab selle uude tüübimuutujasse nimegaU.? U : T: Kui tingimus on tõene (ToliPromise), on tulemuseks olev tüüpU(lahtipakitud tüüp). Vastasel juhul on tulemuseks lihtsalt algne tüüpT.
Kasutus:
type User = { id: number; name: string; };
type UserPromise = Promise
type UnwrappedUser = UnwrapPromise
type UnwrappedNumber = UnwrapPromise
See muster on nii levinud, et TypeScript sisaldab sisseehitatud abitüüpe nagu ReturnType, mis on implementeeritud sama põhimõtte abil, et eraldada funktsiooni tagastustüüp.
Distributiivsed tingimuslikud tüübid: töötamine unioonidega
Tingimuslike tüüpide põnev ja oluline käitumine on see, et nad muutuvad distributiivseks, kui kontrollitav tüüp on "paljas" geneeriline tüübiparameeter. See tähendab, et kui edastate sellele unioonitüübi, rakendatakse tingimust igale uniooni liikmele eraldi ja tulemused kogutakse tagasi uude uniooni.
Vaatleme tüüpi, mis teisendab tüübi selle tüübi massiiviks:
type ToArray
Kui edastame ToArray-le unioonitüübi:
type StrOrNumArray = ToArray
Tulemus ei ole (string | number)[]. Kuna T on paljas tüübiparameeter, jaotatakse tingimus laiali:
ToArraymuutubstring[]-ksToArraymuutubnumber[]-ks
Lõpptulemus on nende üksikute tulemuste unioon: string[] | number[].
See distributiivne omadus on uskumatult kasulik unioonide filtreerimiseks. Näiteks sisseehitatud Extract abitüüp kasutab seda, et valida unioonist T liikmeid, mis on omistatavad tüübile U.
Kui soovite seda distributiivset käitumist vältida, võite mähkida tüübiparameetri ennikusse (tuple) mõlemal pool extends klauslit:
type ToArrayNonDistributive
type StrOrNumArrayUnified = ToArrayNonDistributive
Selle tugeva alusega uurime, kuidas saame konstrueerida dünaamilisi sõnetüüpe.
Dünaamiliste sõnede ehitamine tüübitasemel: malliliteraalide tüübid
TypeScript 4.1-s tutvustatud malliliteraalide tüübid (Template Literal Types) võimaldavad teil defineerida tüüpe, mis on kujundatud sarnaselt JavaScripti malliliteraalidele. Need võimaldavad teil olemasolevatest sõneliteraalide tüüpidest uusi moodustada, neid aheldada ja kombineerida.
Süntaks on täpselt selline, nagu ootaksite:
type World = "World";
type Greeting = `Hello, ${World}!`; // tüüp Greeting on "Hello, World!"
See võib tunduda lihtne, kuid selle jõud peitub selle kombineerimises unioonide ja geneerikutega.
Unioonid ja permutatsioonid
Kui malliliteraali tüüp sisaldab uniooni, laieneb see uueks uniooniks, mis sisaldab kõiki võimalikke sõnepermutatsioone. See on võimas viis hästi defineeritud konstantide hulga genereerimiseks.
Kujutage ette CSS-i veerisomaduste (margin properties) komplekti defineerimist:
type Side = "top" | "right" | "bottom" | "left";
type MarginProperty = `margin-${Side}`;
Tulemuseks saadav tüüp MarginProperty jaoks on:
"margin-top" | "margin-right" | "margin-bottom" | "margin-left"
See on ideaalne tüübikindlate komponendi prop'ide või funktsiooni argumentide loomiseks, kus on lubatud ainult teatud sõnevormingud.
Kombineerimine geneerikutega
Malliliteraalid säravad tõeliselt, kui neid kasutatakse koos geneerikutega. Saate luua tehase-tüüpe (factory types), mis genereerivad uusi sõneliteraalide tüüpe mingi sisendi põhjal.
type MakeEventListener
type UserListener = MakeEventListener<"user">; // "onUserChange"
type ProductListener = MakeEventListener<"product">; // "onProductChange"
See muster on dünaamiliste, tüübikindlate API-de loomise võti. Aga mis siis, kui peame muutma sõne registrit, näiteks muutma `"user"` väärtuseks `"User"`, et saada `"onUserChange"`? Siin tulevad appi sõnetöötluse tüübid.
Tööriistakomplekt: sisseehitatud sõnetöötluse tüübid
Et muuta malliliteraalid veelgi võimsamaks, pakub TypeScript komplekti sisseehitatud tüüpe sõneliteraalide manipuleerimiseks. Need on nagu abifunktsioonid, kuid tüübisüsteemi jaoks.
Registrimuutjad: `Uppercase`, `Lowercase`, `Capitalize`, `Uncapitalize`
Need neli tüüpi teevad täpselt seda, mida nende nimed ütlevad:
Uppercase: Teisendab kogu sõnetüübi suurtähtedesse.type LOUD = Uppercase<"hello">; // "HELLO"Lowercase: Teisendab kogu sõnetüübi väiketähtedesse.type quiet = Lowercase<"WORLD">; // "world"Capitalize: Teisendab sõnetüübi esimese tähe suurtäheks.type Proper = Capitalize<"john">; // "John"Uncapitalize: Teisendab sõnetüübi esimese tähe väiketäheks.type variable = Uncapitalize<"PersonName">; // "personName"
Vaatame uuesti meie eelmist näidet ja täiustame seda, kasutades Capitalize'i, et genereerida konventsionaalseid sündmuste käsitlejate nimesid:
type MakeEventListener
type UserListener = MakeEventListener<"user">; // "onUserChange"
type ProductListener = MakeEventListener<"product">; // "onProductChange"
Nüüd on meil kõik osad olemas. Vaatame, kuidas need kombineeruvad, et lahendada keerulisi, reaalseid probleeme.
Süntees: kõigi kolme kombineerimine täiustatud mustrite jaoks
Siin kohtub teooria praktikaga. Põimides kokku tingimuslikke tüüpe, malliliteraale ja sõnetöötlust, saame ehitada uskumatult keerukaid ja turvalisi tüübidefinitsioone.
Muster 1: täielikult tüübikindel sündmuste edastaja (Event Emitter)
Eesmärk: Luua geneeriline EventEmitter klass meetoditega nagu on(), off() ja emit(), mis on täielikult tüübikindlad. See tähendab:
- Meetoditele edastatud sündmuse nimi peab olema kehtiv sündmus.
- Meetodile
emit()edastatud andmekoorem (payload) peab vastama selle sündmuse jaoks defineeritud tüübile. - Meetodile
on()edastatud tagasikutsefunktsioon (callback) peab aktsepteerima selle sündmuse jaoks õiget andmekoorma tüüpi.
Esmalt defineerime sündmuste nimede ja nende andmekoormate tüüpide vastenduse (map):
interface EventMap {
"user:created": { userId: number; name: string; };
"user:deleted": { userId: number; };
"product:added": { productId: string; price: number; };
}
Nüüd saame ehitada geneerilise EventEmitter klassi. Kasutame geneerilist parameetrit Events, mis peab laiendama meie EventMap struktuuri.
class TypedEventEmitter
private listeners: { [K in keyof Events]?: ((payload: Events[K]) => void)[] } = {};
// `on` meetod kasutab geneerilist tüüpi `K`, mis on meie sündmuste vastenduse (Events map) võti
on
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event]?.push(callback);
}
// `emit` meetod tagab, et andmekoorem vastab sündmuse tüübile
emit
this.listeners[event]?.forEach(callback => callback(payload));
}
}
Loome selle instantsi ja kasutame seda:
const appEvents = new TypedEventEmitter
// See on tüübikindel. Andmekoorem tuletatakse korrektselt kui { userId: number; name: string; }
appEvents.on("user:created", (payload) => {
console.log(`User created: ${payload.name} (ID: ${payload.userId})`);
});
// TypeScript annab siin vea, sest "user:updated" ei ole EventMap'i võti
// appEvents.on("user:updated", () => {}); // Viga!
// TypeScript annab siin vea, sest andmekoormast puudub 'name' omadus
// appEvents.emit("user:created", { userId: 123 }); // Viga!
See muster pakub kompileerimisaegset turvalisust sellele, mis on traditsiooniliselt paljude rakenduste väga dünaamiline ja vigaderohke osa.
Muster 2: tüübikindel ligipääs pesastatud objektide teekonnale (path)
Eesmärk: Luua abitüüp, PathValue, mis suudab määrata väärtuse tüübi pesastatud objektis T, kasutades punkt-notatsiooniga sõneteed P (nt "user.address.city").
See on väga täiustatud muster, mis demonstreerib rekursiivseid tingimuslikke tüüpe.
Siin on implementatsioon, mille me lahti seletame:
type PathValue
? Key extends keyof T
? PathValue
: never
: P extends keyof T
? T[P]
: never;
Jälgime selle loogikat näitega: PathValue
- Algne kutse:
Pon"a.b.c". See vastab malliliteraalile`${infer Key}.${infer Rest}`. Keytuletatakse kui"a".Resttuletatakse kui"b.c".- Esimene rekursioon: Tüüp kontrollib, kas
"a"onMyObject'i võti. Kui jah, kutsub see rekursiivselt väljaPathValue. - Teine rekursioon: Nüüd on
P"b.c". See vastab uuesti malliliteraalile. Keytuletatakse kui"b".Resttuletatakse kui"c".- Tüüp kontrollib, kas
"b"onMyObject["a"]võti ja kutsub rekursiivselt väljaPathValue. - Baasjuhtum: Lõpuks on
P"c". See ei vasta mallile`${infer Key}.${infer Rest}`. Tüübiloogika läheb edasi teise tingimuseni:P extends keyof T ? T[P] : never. - Tüüp kontrollib, kas
"c"onMyObject["a"]["b"]võti. Kui jah, on tulemuseksMyObject["a"]["b"]["c"]. Kui ei, siis on seenever.
Kasutamine koos abifunktsiooniga:
declare function get
const myObject = {
user: {
name: "Alice",
address: {
city: "Wonderland",
zip: 12345
}
}
};
const city = get(myObject, "user.address.city"); // const city: string
const zip = get(myObject, "user.address.zip"); // const zip: number
const invalid = get(myObject, "user.email"); // const invalid: never
See võimas tüüp ennetab käitusaegseid vigu, mis tulenevad trükivigadest teekondades, ja pakub täiuslikku tüübipäringut sügavalt pesastatud andmestruktuuridele, mis on levinud väljakutse globaalsetes rakendustes, mis tegelevad keerukate API vastustega.
Parimad praktikad ja jõudlusega seotud kaalutlused
Nagu iga võimsa tööriista puhul, on oluline neid funktsioone targalt kasutada.
- Eelistage loetavust: Keerulised tüübid võivad kiiresti muutuda loetamatuks. Jaotage need väiksemateks, hästi nimetatud abitüüpideks. Kasutage kommentaare loogika selgitamiseks, täpselt nagu teeksite keeruka käitusaegse koodiga.
- Mõistke
nevertüüpi: Tüüpneveron teie peamine tööriist veaolukordade käsitlemiseks ja unioonide filtreerimiseks tingimuslikes tüüpides. See esindab olekut, mida ei tohiks kunagi esineda. - Olge teadlik rekursioonipiirangutest: TypeScriptil on tüüpide instantseerimisel rekursioonisügavuse piirang. Kui teie tüübid on liiga sügavalt pesastatud või lõpmatult rekursiivsed, annab kompilaator vea. Veenduge, et teie rekursiivsetel tüüpidel oleks selge baasjuhtum.
- Jälgige IDE jõudlust: Äärmiselt keerulised tüübid võivad mõnikord mõjutada TypeScripti keeleserveri jõudlust, mis toob kaasa aeglasema automaatse täiendamise ja tüübikontrolli teie redaktoris. Kui märkate aeglustumist, uurige, kas keerulist tüüpi saab lihtsustada või lahti harutada.
- Teage, millal lõpetada: Need funktsioonid on mõeldud keeruliste tüübikindluse ja arendajakogemuse probleemide lahendamiseks. Ärge kasutage neid lihtsate tüüpide üle-inseneerimiseks. Eesmärk on suurendada selgust ja ohutust, mitte lisada tarbetut keerukust.
Kokkuvõte
Tingimuslikud tüübid, malliliteraalid ja sõnetöötluse tüübid ei ole lihtsalt isoleeritud funktsioonid; need on tihedalt integreeritud süsteem keeruka loogika teostamiseks tüübitasemel. Need annavad meile võimaluse liikuda kaugemale lihtsatest annotatsioonidest ja ehitada süsteeme, mis on sügavalt teadlikud omaenda struktuurist ja piirangutest.
Selle kolmiku valdamisega saate:
- Luua isedokumenteeruvaid API-sid: Tüübid ise muutuvad dokumentatsiooniks, juhendades arendajaid neid õigesti kasutama.
- Kõrvaldada terveid veaklasse: Tüübivead püütakse kinni kompileerimise ajal, mitte kasutajate poolt tootmises.
- Parandada arendajakogemust: Nautige rikkalikku automaatset täiendamist ja reasiseseid veateateid isegi oma koodibaasi kõige dünaamilisemate osade jaoks.
Nende täiustatud võimaluste omaksvõtmine muudab TypeScripti turvavõrgust võimsaks partneriks arenduses. See võimaldab teil kodeerida keerulist äriloogikat ja muutumatuid reegleid otse tüübisüsteemi, tagades, et teie rakendused on robustsemad, hooldatavamad ja skaleeritavamad globaalsele publikule.