Perženkite pagrindinių tipų ribas. Įvaldykite pažangias „TypeScript“ funkcijas, tokias kaip sąlyginiai tipai, šablonų literatai ir eilučių apdorojimas, kad sukurtumėte neįtikėtinai patikimas ir saugias API. Išsamus vadovas pasauliniams kūrėjams.
Atskleidžiant visą „TypeScript“ potencialą: gilus nardymas į sąlyginius tipus, šablonų literatus ir pažangų darbą su eilutėmis
Šiuolaikinio programinės įrangos kūrimo pasaulyje „TypeScript“ išsivystė toli už savo pradinio vaidmens – paprasto „JavaScript“ tipo tikrintuvo. Ji tapo sudėtingu įrankiu tam, ką galima apibūdinti kaip programavimą tipų lygiu. Ši paradigma leidžia kūrėjams rašyti kodą, kuris veikia su pačiais tipais, sukuriant dinamines, savaime dokumentuojamas ir nepaprastai saugias API. Šios revoliucijos pagrindą sudaro trys galingos funkcijos, veikiančios kartu: sąlyginiai tipai, šablonų literalų tipai ir įtaisytų eilučių apdorojimo tipų rinkinys.
Kūrėjams visame pasaulyje, norintiems patobulinti savo „TypeScript“ įgūdžius, šių koncepcijų supratimas nebėra prabanga – tai būtinybė kuriant keičiamo dydžio ir prižiūrimas programas. Šis vadovas nuves jus į gilų nardymą, pradedant nuo pagrindinių principų ir pereinant prie sudėtingų, realių šablonų, kurie demonstruoja bendrą jų galią. Nesvarbu, ar kuriate dizaino sistemą, saugų API klientą, ar sudėtingą duomenų apdorojimo biblioteką, šių funkcijų įvaldymas iš esmės pakeis jūsų „TypeScript“ rašymo būdą.
Pagrindas: sąlyginiai tipai („extends“ trinaris)
Sąlyginis tipas leidžia pasirinkti vieną iš dviejų galimų tipų, atsižvelgiant į tipo ryšio patikrinimą. Jei esate susipažinę su „JavaScript“ trinariniu operatoriumi (condition ? valueIfTrue : valueIfFalse), sintaksė jums iškart pasirodys intuityvi:
type Result = SomeType extends OtherType ? TrueType : FalseType;
Čia extends raktinis žodis veikia kaip mūsų sąlyga. Jis tikrina, ar SomeType gali būti priskirtas OtherType. Išskaidykime jį su paprastu pavyzdžiu.
Pagrindinis pavyzdys: tipo tikrinimas
Įsivaizduokite, kad norime sukurti tipą, kuris nustato true, jei nurodytas tipas T yra eilutė, ir false kitu atveju.
type IsString
Tada galime naudoti šį tipą taip:
type A = IsString<"hello">; // type A is true
type B = IsString<123>; // type B is false
Tai yra pagrindinis statybinis blokas. Tačiau tikroji sąlyginių tipų galia atsiskleidžia, kai jie derinami su infer raktiniu žodžiu.
`infer` galia: tipų išgavimas iš vidaus
infer raktinis žodis pakeičia žaidimo taisykles. Jis leidžia jums deklaruoti naują bendrinio tipo kintamąjį sąlygoje „extends“, efektyviai užfiksuojant dalį tipo, kurį tikrinate. Pagalvokite apie tai kaip apie tipo lygmens kintamojo deklaraciją, kuri gauna savo reikšmę iš šablonų atitikimo.
Klasikinis pavyzdys yra tipo, esančio Promise viduje, išvyniojimas.
type UnwrapPromise
Išanalizuokime tai:
T extends Promise: Tai tikrina, arTyraPromise. Jei taip, „TypeScript“ bando atitikti struktūrą.infer U: Jei atitikimas sėkmingas, „TypeScript“ užfiksuoja tipą, į kurį nustatoPromise, ir įdeda jį į naują tipo kintamąjį, pavadintąU.? U : T: Jei sąlyga yra teisinga (TbuvoPromise), gaunamas tipas yraU(išvyniotas tipas). Priešingu atveju gaunamas tipas yra tik pradinis tipasT.
Naudojimas:
type User = { id: number; name: string; };
type UserPromise = Promise
type UnwrappedUser = UnwrapPromise
type UnwrappedNumber = UnwrapPromise
Šis šablonas yra toks dažnas, kad „TypeScript“ apima įtaisytus naudingumo tipus, tokius kaip ReturnType, kuris įgyvendinamas naudojant tą patį principą, kad būtų galima išgauti funkcijos grąžinimo tipą.
Distributyvūs sąlyginiai tipai: darbas su sąjungomis
Žavus ir esminis sąlyginių tipų elgesys yra tas, kad jie tampa distributyvūs, kai tikrinamas tipas yra „nuogas“ bendrinis tipo parametras. Tai reiškia, kad jei perduosite sąjungos tipą į jį, sąlyga bus taikoma kiekvienam sąjungos nariui atskirai, o rezultatai bus surinkti atgal į naują sąjungą.
Apsvarstykite tipą, kuris konvertuoja tipą į to tipo masyvą:
type ToArray
Jei perduosime sąjungos tipą į ToArray:
type StrOrNumArray = ToArray
Rezultatas nėra (string | number)[]. Kadangi T yra nuogas tipo parametras, sąlyga yra paskirstyta:
ToArraytampastring[]ToArraytampanumber[]
Galutinis rezultatas yra šių atskirų rezultatų sąjunga: string[] | number[].
Ši distributyvinė savybė yra neįtikėtinai naudinga filtruojant sąjungas. Pavyzdžiui, įtaisytas naudingumo tipas Extract naudoja tai norėdamas pasirinkti narius iš sąjungos T, kurie yra priskirtini U.
Jei jums reikia užkirsti kelią šiam distributyviniam elgesiui, galite apvynioti tipo parametrą į aibę abiejose extends sąlygos pusėse:
type ToArrayNonDistributive
type StrOrNumArrayUnified = ToArrayNonDistributive
Turėdami tvirtą pagrindą, išnagrinėkime, kaip galime sukurti dinaminius eilučių tipus.
Dinaminių eilučių kūrimas tipų lygiu: šablonų literalų tipai
Pristatyti „TypeScript 4.1“, šablonų literalų tipai leidžia apibrėžti tipus, kurie yra suformuoti kaip „JavaScript“ šablonų literalų eilutės. Jie leidžia jums sujungti, derinti ir generuoti naujus eilučių literalų tipus iš esamų.
Sintaksė yra būtent tokia, kokios tikėtumėtės:
type World = "World";
type Greeting = `Hello, ${World}!`; // type Greeting is "Hello, World!"
Tai gali atrodyti paprasta, tačiau jos galia slypi derinant ją su sąjungomis ir generiniais tipais.
Sąjungos ir perstatymai
Kai šablonų literalų tipas apima sąjungą, jis išsiplečia į naują sąjungą, kurioje yra kiekvienas įmanomas eilučių perstatymas. Tai yra galingas būdas generuoti gerai apibrėžtų konstantų rinkinį.
Įsivaizduokite, kad apibrėžiate CSS paraštės savybių rinkinį:
type Side = "top" | "right" | "bottom" | "left";
type MarginProperty = `margin-${Side}`;
Gaunamas MarginProperty tipas yra:
"margin-top" | "margin-right" | "margin-bottom" | "margin-left"
Tai puikiai tinka kuriant saugias komponentų savybes arba funkcijos argumentus, kai leidžiami tik konkretūs eilučių formatai.
Derinimas su generiniais tipais
Šablonų literatai tikrai spindi, kai naudojami su generiniais tipais. Galite kurti gamyklinius tipus, kurie generuoja naujus eilučių literalų tipus pagal tam tikrą įvestį.
type MakeEventListener
type UserListener = MakeEventListener<"user">; // "onUserChange"
type ProductListener = MakeEventListener<"product">; // "onProductChange"
Šis šablonas yra raktas į dinamiškų, saugių API kūrimą. Bet ką daryti, jei mums reikia pakeisti eilutės atvejį, pavyzdžiui, pakeisti "user" į "User", kad gautume "onUserChange"? Čia atsiranda eilučių apdorojimo tipai.
Įrankių rinkinys: įtaisyti eilučių apdorojimo tipai
Kad šablonų literatai būtų dar galingesni, „TypeScript“ pateikia įtaisytų tipų rinkinį, skirtą eilučių literatams apdoroti. Jie yra kaip naudingumo funkcijos, bet skirtos tipų sistemai.
Atvejų modifikatoriai: `Uppercase`, `Lowercase`, `Capitalize`, `Uncapitalize`
Šie keturi tipai daro būtent tai, ką rodo jų pavadinimai:
Uppercase: Konvertuoja visą eilutės tipą į didžiąsias raides.type LOUD = Uppercase<"hello">; // "HELLO"Lowercase: Konvertuoja visą eilutės tipą į mažąsias raides.type quiet = Lowercase<"WORLD">; // "world"Capitalize: Konvertuoja pirmąjį eilutės tipo simbolį į didžiąją raidę.type Proper = Capitalize<"john">; // "John"Uncapitalize: Konvertuoja pirmąjį eilutės tipo simbolį į mažąją raidę.type variable = Uncapitalize<"PersonName">; // "personName"
Pakartotinai apsilankykime ankstesniame pavyzdyje ir patobulinkime jį naudodami Capitalize, kad sugeneruotume įprastus įvykių apdorojimo priemonių pavadinimus:
type MakeEventListener
type UserListener = MakeEventListener<"user">; // "onUserChange"
type ProductListener = MakeEventListener<"product">; // "onProductChange"
Dabar turime visas dalis. Pažiūrėkime, kaip jos derinamos sprendžiant sudėtingas, realias problemas.
Sintezė: visų trijų derinimas pažangiems šablonams
Čia teorija susitinka su praktika. Susiejant sąlyginius tipus, šablonų literatus ir eilučių apdorojimą, galime sukurti neįtikėtinai sudėtingus ir saugius tipų apibrėžimus.
1 šablonas: visiškai saugus įvykių skleistuvas
Tikslas: sukurti bendrinę EventEmitter klasę su metodais, tokiais kaip on(), off() ir emit(), kurie yra visiškai saugūs. Tai reiškia:
- Įvykio pavadinimas, perduotas metodams, turi būti galiojantis įvykis.
- Naudingoji apkrova, perduota į
emit(), turi atitikti tam įvykiui apibrėžtą tipą. - Atgalinio iškvietimo funkcija, perduota į
on(), turi priimti teisingą naudingosios apkrovos tipą tam įvykiui.
Pirmiausia apibrėžiame įvykių pavadinimų ir jų naudingųjų apkrovų tipų žemėlapį:
interface EventMap {
"user:created": { userId: number; name: string; };
"user:deleted": { userId: number; };
"product:added": { productId: string; price: number; };
}
Dabar galime sukurti bendrinę EventEmitter klasę. Naudosime bendrinį parametrą Events, kuris turi išplėsti mūsų EventMap struktūrą.
class TypedEventEmitter
private listeners: { [K in keyof Events]?: ((payload: Events[K]) => void)[] } = {};
// Metodas `on` naudoja bendrinį `K`, kuris yra mūsų Events žemėlapio raktas
on
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event]?.push(callback);
}
// Metodas `emit` užtikrina, kad naudingoji apkrova atitiktų įvykio tipą
emit
this.listeners[event]?.forEach(callback => callback(payload));
}
}
Įkūnysime ir naudosime jį:
const appEvents = new TypedEventEmitter
// Tai yra saugus. Naudingoji apkrova teisingai nustatoma kaip { userId: number; name: string; }
appEvents.on("user:created", (payload) => {
console.log(`User created: ${payload.name} (ID: ${payload.userId})`);
});
// „TypeScript“ čia suklys, nes „user:updated“ nėra EventMap raktas
// appEvents.on("user:updated", () => {}); // Klaida!
// „TypeScript“ čia suklys, nes naudingojoje apkrovoje trūksta savybės „name“
// appEvents.emit("user:created", { userId: 123 }); // Klaida!
Šis šablonas užtikrina kompiliavimo laiko saugumą tai, kas tradiciškai yra labai dinamiška ir klaidinga daugelio programų dalis.
2 šablonas: saugus kelio prieiga prie įdėtų objektų
Tikslas: sukurti naudingumo tipą, PathValue, kuris gali nustatyti reikšmės tipą įdėtame objekte T naudojant taško žymėjimo eilutės kelią P (pvz., "user.address.city").
Tai yra labai pažangus šablonas, kuris demonstruoja rekursyvius sąlyginius tipus.
Štai įgyvendinimas, kurį suskaidysime:
type PathValue
? Key extends keyof T
? PathValue
: never
: P extends keyof T
? T[P]
: never;
Išnagrinėkime jo logiką su pavyzdžiu: PathValue
- Pradinis iškvietimas:
Pyra"a.b.c". Tai atitinka šablonų literatą`${infer Key}.${infer Rest}`. Keynustatomas kaip"a".Restnustatomas kaip"b.c".- Pirmoji rekursija: Tipas tikrina, ar
"a"yraMyObjectraktas. Jei taip, jis rekursyviai iškviečiaPathValue. - Antroji rekursija: Dabar
Pyra"b.c". Jis vėl atitinka šablonų literatą. Keynustatomas kaip"b".Restnustatomas kaip"c".- Tipas tikrina, ar
"b"yraMyObject["a"]raktas, ir rekursyviai iškviečiaPathValue. - Bazinis atvejis: Galiausiai,
Pyra"c". Tai neatitinka`${infer Key}.${infer Rest}`. Tipo logika pereina prie antros sąlygos:P extends keyof T ? T[P] : never. - Tipas tikrina, ar
"c"yraMyObject["a"]["b"]raktas. Jei taip, rezultatas yraMyObject["a"]["b"]["c"]. Jei ne, tai yranever.
Naudojimas su pagalbinė funkcija:
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
Šis galingas tipas apsaugo nuo vykdymo laiko klaidų dėl klaidų keliuose ir užtikrina puikų tipų nustatymą giliai įdėtoms duomenų struktūroms, o tai yra dažnas iššūkis pasaulinėse programose, susijusiose su sudėtingais API atsakymais.
Geriausia praktika ir našumo svarstymai
Kaip ir su bet kuriuo galingu įrankiu, svarbu naudoti šias funkcijas išmintingai.
- Prioritetas skaitymui: Sudėtingi tipai gali greitai tapti neskaitomi. Suskaidykite juos į mažesnius, gerai pavadintus pagalbinius tipus. Naudokite komentarus, kad paaiškintumėte logiką, kaip darytumėte su sudėtingu vykdymo laiko kodu.
- Supraskite `never` tipą: `never` tipas yra jūsų pagrindinis įrankis klaidų būsenoms apdoroti ir sąjungoms filtruoti sąlyginiuose tipuose. Jis atspindi būseną, kuri niekada neturėtų įvykti.
- Saugokitės rekursijos apribojimų: „TypeScript“ turi rekursijos gylio apribojimą tipo įkūnijimui. Jei jūsų tipai yra per giliai įdėti arba begaliniai rekursyvūs, kompiliatorius suklys. Įsitikinkite, kad jūsų rekursyvūs tipai turi aiškų bazinį atvejį.
- Stebėkite IDE našumą: Ypač sudėtingi tipai kartais gali turėti įtakos „TypeScript“ kalbos serverio našumui, todėl lėčiau automatiškai užbaigiami ir tikrinami tipai jūsų redaktoriuje. Jei jaučiate sulėtėjimą, pažiūrėkite, ar sudėtingą tipą galima supaprastinti arba suskaidyti.
- Žinokite, kada sustoti: Šios funkcijos skirtos spręsti sudėtingas tipo saugumo ir kūrėjo patirties problemas. Nenaudokite jų, kad per daug suprojektuotumėte paprastus tipus. Tikslas yra pagerinti aiškumą ir saugumą, o ne pridėti nereikalingo sudėtingumo.
Išvada
Sąlyginiai tipai, šablonų literatai ir eilučių apdorojimo tipai nėra tik izoliuotos funkcijos; jie yra glaudžiai integruota sistema, skirta sudėtingai logikai atlikti tipų lygiu. Jie suteikia mums galimybę peržengti paprastas anotacijas ir sukurti sistemas, kurios giliai žino savo struktūrą ir apribojimus.
Įvaldę šį trejetą, galite:
- Kurti savaime dokumentuojamas API: Patys tipai tampa dokumentacija, nukreipiančia kūrėjus juos teisingai naudoti.
- Pašalinti visas klaidų klases: Tipo klaidos pagaunamos kompiliavimo metu, o ne vartotojai gamyboje.
- Pagerinti kūrėjo patirtį: Mėgaukitės turtingu automatiniu užbaigimu ir įterptais klaidų pranešimais net ir dinamiškiausioms kodo bazės dalims.
Šių pažangių galimybių įsisavinimas paverčia „TypeScript“ iš apsauginio tinklo į galingą partnerį kuriant. Tai leidžia jums užkoduoti sudėtingą verslo logiką ir invariantus tiesiogiai į tipų sistemą, užtikrinant, kad jūsų programos būtų patikimesnės, prižiūrimos ir keičiamo dydžio pasaulinei auditorijai.