Obvladajte napredne funkcije TypeScripta: pogojne tipe, šablonske literale in manipulacijo nizov. Zgradite robustne, tipsko varne API-je. Celovit vodnik.
Odklepanje polnega potenciala TypeScripta: Poglobljen vpogled v pogojne tipe, šablonske literale in napredno manipulacijo nizov
V svetu sodobnega razvoja programske opreme se je TypeScript razvil daleč preko svoje začetne vloge preprostega preverjalnika tipov za JavaScript. Postal je sofisticirano orodje za to, kar lahko opišemo kot programiranje na ravni tipov. Ta paradigma omogoča razvijalcem pisanje kode, ki deluje na samih tipih, s čimer ustvarja dinamične, samostojno dokumentirane in izjemno varne API-je. V središču te revolucije so tri močne funkcije, ki delujejo skupaj: pogojni tipi, šablonski literali in nabor lastnih tipov za manipulacijo nizov.
Za razvijalce po vsem svetu, ki želijo izboljšati svoje znanje TypeScripta, razumevanje teh konceptov ni več luksuz – je nujnost za gradnjo razširljivih in vzdržljivih aplikacij. Ta vodnik vas bo popeljal v poglobljen vpogled, začenši z osnovnimi načeli in gradnjo do kompleksnih, resničnih vzorcev, ki prikazujejo njihovo kombinirano moč. Ne glede na to, ali gradite oblikovalski sistem, tipsko varnega API odjemalca ali kompleksno knjižnico za obdelavo podatkov, bo obvladovanje teh funkcij bistveno spremenilo način pisanja TypeScripta.
Temelj: Pogojni tipi (Ternarni operator `extends`)
V svojem bistvu vam pogojni tip omogoča izbiro enega od dveh možnih tipov na podlagi preverjanja razmerja med tipi. Če ste seznanjeni z JavaScriptovim ternarnim operatorjem (pogoj ? vrednostČePravilno : vrednostČeNapačno), boste sintakso takoj razumeli:
type Result = SomeType extends OtherType ? TrueType : FalseType;
Tukaj ključna beseda extends deluje kot naš pogoj. Preveri, ali je SomeType dodeljiv tipu OtherType. Poglejmo si to s preprostim primerom.
Osnovni primer: Preverjanje tipa
Predstavljajte si, da želimo ustvariti tip, ki se razreši v true, če je določen tip T niz, in false v nasprotnem primeru.
type IsString
Ta tip lahko nato uporabimo takole:
type A = IsString<"hello">; // tip A je true
type B = IsString<123>; // tip B je false
To je temeljni gradnik. Vendar pa se prava moč pogojnih tipov sprosti, ko jih kombiniramo s ključno besedo infer.
Moč `infer`: Pridobivanje tipov iz notranjosti
Ključna beseda infer spreminja pravila igre. Omogoča vam deklaracijo nove generične spremenljivke tipa znotraj klavzule extends, s čimer učinkovito zajamete del tipa, ki ga preverjate. Pomislite na to kot na deklaracijo spremenljivke na ravni tipa, ki pridobi svojo vrednost iz ujemanja vzorcev.
Klasičen primer je razpakiranje tipa, vsebovanega v Promise.
type UnwrapPromise
Analizirajmo to:
T extends Promise: To preveri, ali jeTPromise. Če je, TypeScript poskuša ujemati strukturo.infer U: Če je ujemanje uspešno, TypeScript zajame tip, v katerega sePromiserazreši, in ga shrani v novo spremenljivko tipaU.? U : T: Če je pogoj resničen (Tje bilPromise), je rezultat tipU(razpakiran tip). V nasprotnem primeru je rezultat le prvotni tipT.
Uporaba:
type User = { id: number; name: string; };
type UserPromise = Promise
type UnwrappedUser = UnwrapPromise
type UnwrappedNumber = UnwrapPromise
Ta vzorec je tako pogost, da TypeScript vključuje vgrajene pripomožne tipe, kot je ReturnType, ki je implementiran z uporabo istega načela za pridobivanje povratnega tipa funkcije.
Distributivni pogojni tipi: Delo z unijami
Fascinantna in ključna lastnost pogojnih tipov je, da postanejo distributivni, ko je preverjani tip "gol" generični tipski parameter. To pomeni, da če mu posredujete unijo tipov, se bo pogoj uporabljal za vsakega člana unije posebej, rezultati pa se bodo zbrali nazaj v novo unijo.
Razmislite o tipu, ki pretvori tip v polje tega tipa:
type ToArray
Če posredujemo unijo tipov v ToArray:
type StrOrNumArray = ToArray
Rezultat ni (string | number)[]. Ker je T gol tipski parameter, se pogoj distribuira:
ToArraypostanestring[]ToArraypostanenumber[]
Končni rezultat je unija teh posameznih rezultatov: string[] | number[].
Ta distributivna lastnost je neverjetno uporabna za filtriranje unij. Na primer, vgrajeni pripomožni tip Extract to uporablja za izbiro članov iz unije T, ki so dodeljivi tipu U.
Če želite preprečiti to distributivno obnašanje, lahko parameter tipa ovijete v terko na obeh straneh klavzule extends:
type ToArrayNonDistributive
type StrOrNumArrayUnified = ToArrayNonDistributive
S tem trdnim temeljem raziščimo, kako lahko konstruiramo dinamične tipske nize.
Gradnja dinamičnih nizov na ravni tipa: Šablonski literali
Uvedeni v TypeScriptu 4.1, šablonski literali vam omogočajo definiranje tipov, ki so oblikovani kot JavaScriptovi šablonski nizovi. Omogočajo vam združevanje, kombiniranje in generiranje novih tipov nizov iz obstoječih.
Sintaksa je natanko takšna, kot bi pričakovali:
type World = "World";
type Greeting = `Hello, ${World}!`; // tip Greeting je "Hello, World!"
To se morda zdi preprosto, vendar je njegova moč v kombinaciji z unijami in generiki.
Unije in permutacije
Ko šablonski literalni tip vključuje unijo, se razširi v novo unijo, ki vsebuje vsako možno permutacijo niza. To je močan način za generiranje nabora dobro definiranih konstant.
Predstavljajte si definiranje nabora lastnosti CSS marže:
type Side = "top" | "right" | "bottom" | "left";
type MarginProperty = `margin-${Side}`;
Rezultirajoči tip za MarginProperty je:
"margin-top" | "margin-right" | "margin-bottom" | "margin-left"
To je popolno za ustvarjanje tipsko varnih rekvizitov komponent ali argumentov funkcij, kjer so dovoljeni le določeni formati nizov.
Kombiniranje z generiki
Šablonski literali resnično zasijejo, ko se uporabljajo z generiki. Ustvarite lahko tovarniške tipe, ki generirajo nove tipske literale na podlagi nekega vnosa.
type MakeEventListener
type UserListener = MakeEventListener<"user">; // "onUserChange"
type ProductListener = MakeEventListener<"product">; // "onProductChange"
Ta vzorec je ključ do ustvarjanja dinamičnih, tipsko varnih API-jev. Kaj pa, če moramo spremeniti velikost črk v nizu, na primer spremeniti "user" v "User", da dobimo "onUserChange"? Tu pridejo do izraza tipi za manipulacijo nizov.
Orodje: Vgrajeni tipi za manipulacijo nizov
Da bi bili šablonski literali še močnejši, TypeScript ponuja nabor vgrajenih tipov za manipulacijo nizov. Ti so kot pripomožne funkcije, vendar za sistem tipov.
Spremembe velikosti črk: `Uppercase`, `Lowercase`, `Capitalize`, `Uncapitalize`
Ti štirje tipi delajo natanko to, kar njihova imena nakazujejo:
Uppercase: Pretvoriti celoten tip niza v velike črke.type LOUD = Uppercase<"hello">; // "HELLO"Lowercase: Pretvoriti celoten tip niza v male črke.type quiet = Lowercase<"WORLD">; // "world"Capitalize: Pretvoriti prvi znak tipa niza v veliko črko.type Proper = Capitalize<"john">; // "John"Uncapitalize: Pretvoriti prvi znak tipa niza v malo črko.type variable = Uncapitalize<"PersonName">; // "personName"
Poglejmo si naš prejšnji primer in ga izboljšajmo z uporabo Capitalize za generiranje običajnih imen obravnavalcev dogodkov:
type MakeEventListener
type UserListener = MakeEventListener<"user">; // "onUserChange"
type ProductListener = MakeEventListener<"product">; // "onProductChange"
Sedaj imamo vse dele. Poglejmo, kako se kombinirajo za reševanje kompleksnih problemov iz resničnega sveta.
Sinteza: Kombiniranje vseh treh za napredne vzorce
Tu se teorija sreča s prakso. S prepletanjem pogojnih tipov, šablonskih literalov in manipulacije nizov lahko zgradimo izjemno sofisticirane in varne definicije tipov.
Vzorec 1: Popolnoma tipsko varen oddajnik dogodkov
Cilj: Ustvariti generično razred EventEmitter z metodami, kot so on(), off() in emit(), ki so popolnoma tipsko varne. To pomeni:
- Ime dogodka, posredovano metodam, mora biti veljaven dogodek.
- Tovorni del, posredovan v
emit(), mora ustrezati tipu, določenemu za ta dogodek. - Povratna funkcija, posredovana v
on(), mora sprejeti pravilen tip tovornega dela za ta dogodek.
Najprej definiramo preslikavo imen dogodkov na njihove tipe tovornega dela:
interface EventMap {
"user:created": { userId: number; name: string; };
"user:deleted": { userId: number; };
"product:added": { productId: string; price: number; };
}
Sedaj lahko zgradimo generični razred EventEmitter. Uporabili bomo generični parameter Events, ki mora razširjati našo strukturo EventMap.
class TypedEventEmitter
private listeners: { [K in keyof Events]?: ((payload: Events[K]) => void)[] } = {};
// Metoda `on` uporablja generični `K`, ki je ključ naše mape dogodkov
on
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event]?.push(callback);
}
// Metoda `emit` zagotavlja, da tovorni del ustreza tipu dogodka
emit
this.listeners[event]?.forEach(callback => callback(payload));
}
}
Instancirajmo in uporabimo ga:
const appEvents = new TypedEventEmitter
// To je tipsko varno. Tovorni del je pravilno sklepan kot { userId: number; name: string; }
appEvents.on("user:created", (payload) => {
console.log(`User created: ${payload.name} (ID: ${payload.userId})`);
});
// TypeScript bo tu javil napako, ker "user:updated" ni ključ v EventMap
// appEvents.on("user:updated", () => {}); // Napaka!
// TypeScript bo tu javil napako, ker tovoru manjka lastnost 'name'
// appEvents.emit("user:created", { userId: 123 }); // Napaka!
Ta vzorec zagotavlja varnost v času prevajanja za tisto, kar je tradicionalno zelo dinamičen in nagnjen k napakam del mnogih aplikacij.
Vzorec 2: Tipsko varen dostop do poti za ugnezdene objekte
Cilj: Ustvariti pripomožni tip, PathValue, ki lahko določi tip vrednosti v ugnezdenem objektu T z uporabo poti niza z notacijo pik P (npr. "user.address.city").
To je zelo napreden vzorec, ki prikazuje rekurzivne pogojne tipe.
Tukaj je implementacija, ki jo bomo razčlenili:
type PathValue
? Key extends keyof T
? PathValue
: never
: P extends keyof T
? T[P]
: never;
Sledimo njegovi logiki s primerom: PathValue
- Začetni klic:
Pje"a.b.c". To se ujema s šablonskim literalom`${infer Key}.${infer Rest}`. Keyje sklepan kot"a".Restje sklepan kot"b.c".- Prva rekurzija: Tip preveri, ali je
"a"ključ objektaMyObject. Če je, rekurzivno pokličePathValue. - Druga rekurzija: Sedaj je
P"b.c". Ponovno se ujema s šablonskim literalom. Keyje sklepan kot"b".Restje sklepan kot"c".- Tip preveri, ali je
"b"ključ objektaMyObject["a"]in rekurzivno pokličePathValue. - Osnovni primer: Končno je
P"c". To se ne ujema z`${infer Key}.${infer Rest}`. Logika tipa preide na drugi pogoj:P extends keyof T ? T[P] : never. - Tip preveri, ali je
"c"ključ objektaMyObject["a"]["b"]. Če je, je rezultatMyObject["a"]["b"]["c"]. Če ni, jenever.
Uporaba s pomožno funkcijo:
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
Ta močan tip preprečuje napake med izvajanjem zaradi tipkarskih napak v poteh in zagotavlja popolno sklepanje tipov za globoko ugnezdene podatkovne strukture, kar je pogost izziv v globalnih aplikacijah, ki obravnavajo kompleksne API odzive.
Najboljše prakse in premisleki o zmogljivosti
Kot pri vsakem močnem orodju je pomembno, da te funkcije uporabljamo pametno.
- Dajte prednost berljivosti: Kompleksni tipi lahko hitro postanejo neberljivi. Razčlenite jih na manjše, dobro poimenovane pomožne tipe. Uporabite komentarje za razlago logike, tako kot bi to storili s kompleksno kodo med izvajanjem.
- Razumeti tip `never`: Tip
neverje vaše primarno orodje za obravnavanje stanj napak in filtriranje unij v pogojnih tipih. Predstavlja stanje, ki se ne bi smelo nikoli zgoditi. - Pazite na omejitve rekurzije: TypeScript ima omejitev globine rekurzije za instanciacijo tipov. Če so vaši tipi preveč globoko ugnezdeni ali neskončno rekurzivni, bo prevajalnik javil napako. Zagotovite, da imajo vaši rekurzivni tipi jasen osnovni primer.
- Spremljajte zmogljivost IDE: Izjemno kompleksni tipi lahko včasih vplivajo na zmogljivost jezikovnega strežnika TypeScript, kar vodi do počasnejšega samodejnega dokončanja in preverjanja tipov v vašem urejevalniku. Če opazite upočasnitve, preverite, ali je mogoče kompleksni tip poenostaviti ali razčleniti.
- Vedeti, kdaj prenehati: Te funkcije so namenjene reševanju kompleksnih problemov varnosti tipov in uporabniške izkušnje razvijalcev. Ne uporabljajte jih za prekomerno inženiring preprostih tipov. Cilj je povečati jasnost in varnost, ne dodajati nepotrebne kompleksnosti.
Zaključek
Pogojni tipi, šablonski literali in tipi za manipulacijo nizov niso le izolirane funkcije; so tesno integriran sistem za izvajanje sofisticirane logike na ravni tipov. Omogočajo nam, da presežemo preproste anotacije in zgradimo sisteme, ki se globoko zavedajo svoje strukture in omejitev.
Z obvladovanjem tega tria lahko:
- Ustvarite samostojno dokumentirane API-je: Sami tipi postanejo dokumentacija, ki razvijalce vodi k pravilni uporabi.
- Odpravite celotne razrede napak: Napake tipov so ulovljene v času prevajanja, ne s strani uporabnikov v produkciji.
- Izboljšate uporabniško izkušnjo razvijalcev: Uživajte v bogatem samodejnem dokončevanju in vgrajenih sporočilih o napakah tudi za najbolj dinamične dele vaše kode.
S sprejetjem teh naprednih zmogljivosti se TypeScript spremeni iz varnostne mreže v močnega partnerja pri razvoju. Omogoča vam kodiranje kompleksne poslovne logike in invariant neposredno v sistem tipov, kar zagotavlja, da so vaše aplikacije bolj robustne, vzdržljive in razširljive za globalno občinstvo.