Išnagrinėkite TypeScript nominalaus ženklinimo techniką, skirtą sukurti nepermatomus tipus, pagerinti tipų saugumą ir išvengti nenumatytų tipų pakeitimų. Sužinokite apie praktinį įgyvendinimą ir pažangius naudojimo atvejus.
TypeScript nominalūs prekių ženklai: nepermatomų tipų apibrėžimai didesniam tipų saugumui
TypeScript, nors ir siūlo statinį tipizavimą, pirmiausia naudoja struktūrinį tipizavimą. Tai reiškia, kad tipai laikomi suderinamais, jei jie turi tą pačią formą, nepaisant jų deklaruotų pavadinimų. Nors tai lankstu, kartais tai gali lemti nenumatytus tipų pakeitimus ir sumažinti tipų saugumą. Nominalus ženklinimas, taip pat žinomas kaip nepermatomų tipų apibrėžimai, siūlo būdą pasiekti tvirtesnę tipų sistemą, artimesnę nominaliam tipizavimui, „TypeScript“ aplinkoje. Šis metodas naudoja išmanias technikas, kad tipai elgtųsi taip, lyg jie turėtų unikalius pavadinimus, taip užkertant kelią atsitiktiniams susimaišymams ir užtikrinant kodo teisingumą.
Struktūrinio ir nominalaus tipizavimo supratimas
Prieš pradedant gilintis į nominalų ženklinimą, svarbu suprasti skirtumą tarp struktūrinio ir nominalaus tipizavimo.
Struktūrinis tipizavimas
Struktūriniame tipizavime du tipai laikomi suderinamais, jei jie turi tą pačią struktūrą (t. y. tas pačias savybes su tais pačiais tipais). Panagrinėkime šį „TypeScript“ pavyzdį:
interface Kilogram { value: number; }
interface Gram { value: number; }
const kg: Kilogram = { value: 10 };
const g: Gram = { value: 10000 };
// TypeScript tai leidžia, nes abu tipai turi tą pačią struktūrą
const kg2: Kilogram = g;
console.log(kg2);
Nors `Kilogram` ir `Gram` reiškia skirtingus matavimo vienetus, „TypeScript“ leidžia priskirti `Gram` objektą `Kilogram` kintamajam, nes abu turi `value` savybę, kurios tipas yra `number`. Tai gali sukelti loginių klaidų jūsų kode.
Nominalus tipizavimas
Priešingai, nominalus tipizavimas laiko du tipus suderinamais tik tada, jei jie turi tą patį pavadinimą arba jei vienas yra aiškiai išvestas iš kito. Tokios kalbos kaip „Java“ ir „C#“ daugiausia naudoja nominalų tipizavimą. Jei „TypeScript“ naudotų nominalų tipizavimą, aukščiau pateiktas pavyzdys sukeltų tipo klaidą.
Nominalaus ženklinimo poreikis „TypeScript“
„TypeScript“ struktūrinis tipizavimas paprastai yra naudingas dėl savo lankstumo ir naudojimo paprastumo. Tačiau yra situacijų, kai reikia griežtesnio tipų tikrinimo, kad būtų išvengta loginių klaidų. Nominalus ženklinimas suteikia sprendimą, kaip pasiekti šį griežtesnį tikrinimą neprarandant „TypeScript“ privalumų.
Apsvarstykite šiuos scenarijus:
- Valiutos tvarkymas: `USD` ir `EUR` sumų atskyrimas, siekiant išvengti atsitiktinio valiutų sumaišymo.
- Duomenų bazių ID: Užtikrinimas, kad `UserID` nebūtų atsitiktinai naudojamas ten, kur tikimasi `ProductID`.
- Matavimo vienetai: `Meters` ir `Feet` atskyrimas, siekiant išvengti neteisingų skaičiavimų.
- Saugūs duomenys: Paprasto teksto `Password` ir užšifruoto `PasswordHash` atskyrimas, siekiant išvengti atsitiktinio jautrios informacijos atskleidimo.
Kiekvienu iš šių atvejų struktūrinis tipizavimas gali sukelti klaidų, nes pagrindinė reprezentacija (pvz., skaičius ar eilutė) abiem tipams yra ta pati. Nominalus ženklinimas padeda užtikrinti tipų saugumą, padarydamas šiuos tipus skirtingais.
Nominalių prekių ženklų įgyvendinimas „TypeScript“
Yra keletas būdų, kaip įgyvendinti nominalų ženklinimą „TypeScript“. Išnagrinėsime įprastą ir efektyvią techniką, naudojant sankirtas ir unikalius simbolius.
Naudojant sankirtas ir unikalius simbolius
Ši technika apima unikalaus simbolio sukūrimą ir jo sankirtą su baziniu tipu. Unikalus simbolis veikia kaip „prekės ženklas“, kuris atskiria tipą nuo kitų, turinčių tą pačią struktūrą.
// Apibrėžiame unikalų simbolį Kilogram prekės ženklui
const kilogramBrand: unique symbol = Symbol();
// Apibrėžiame Kilogram tipą, pažymėtą unikaliu simboliu
type Kilogram = number & { readonly [kilogramBrand]: true };
// Apibrėžiame unikalų simbolį Gram prekės ženklui
const gramBrand: unique symbol = Symbol();
// Apibrėžiame Gram tipą, pažymėtą unikaliu simboliu
type Gram = number & { readonly [gramBrand]: true };
// Pagalbinė funkcija Kilogram reikšmėms kurti
const Kilogram = (value: number) => value as Kilogram;
// Pagalbinė funkcija Gram reikšmėms kurti
const Gram = (value: number) => value as Gram;
const kg: Kilogram = Kilogram(10);
const g: Gram = Gram(10000);
// Dabar tai sukels TypeScript klaidą
// const kg2: Kilogram = g; // Tipas 'Gram' nėra priskiriamas tipui 'Kilogram'.
console.log(kg, g);
Paaiškinimas:
- Mes apibrėžiame unikalų simbolį naudodami `Symbol()`. Kiekvienas `Symbol()` iškvietimas sukuria unikalią reikšmę, užtikrinančią, kad mūsų prekių ženklai būtų skirtingi.
- Mes apibrėžiame `Kilogram` ir `Gram` tipus kaip `number` sankirtą su objektu, kuriame yra unikalus simbolis kaip raktas su `true` reikšme. `readonly` modifikatorius užtikrina, kad prekės ženklas negali būti modifikuotas po sukūrimo.
- Mes naudojame pagalbines funkcijas (`Kilogram` ir `Gram`) su tipų tvirtinimais (`as Kilogram` ir `as Gram`), kad sukurtume paženklintų tipų reikšmes. Tai būtina, nes „TypeScript“ negali automatiškai nustatyti paženklinto tipo.
Dabar „TypeScript“ teisingai praneša apie klaidą, kai bandote priskirti `Gram` reikšmę `Kilogram` kintamajam. Tai užtikrina tipų saugumą ir apsaugo nuo atsitiktinių susimaišymų.
Bendrinis ženklinimas pakartotiniam naudojimui
Kad nereikėtų kartoti ženklinimo modelio kiekvienam tipui, galite sukurti bendrinį pagalbinį tipą:
type Brand = K & { readonly __brand: unique symbol; };
// Apibrėžiame Kilogram naudojant bendrinį Brand tipą
type Kilogram = Brand;
// Apibrėžiame Gram naudojant bendrinį Brand tipą
type Gram = Brand;
// Pagalbinė funkcija Kilogram reikšmėms kurti
const Kilogram = (value: number) => value as Kilogram;
// Pagalbinė funkcija Gram reikšmėms kurti
const Gram = (value: number) => value as Gram;
const kg: Kilogram = Kilogram(10);
const g: Gram = Gram(10000);
// Tai vis tiek sukels TypeScript klaidą
// const kg2: Kilogram = g; // Tipas 'Gram' nėra priskiriamas tipui 'Kilogram'.
console.log(kg, g);
Šis metodas supaprastina sintaksę ir palengvina nuoseklų paženklintų tipų apibrėžimą.
Pažangūs naudojimo atvejai ir svarstymai
Objektų ženklinimas
Nominalus ženklinimas taip pat gali būti taikomas objektų tipams, ne tik primityviems tipams, tokiems kaip skaičiai ar eilutės.
interface User {
id: number;
name: string;
}
const UserIDBrand: unique symbol = Symbol();
type UserID = number & { readonly [UserIDBrand]: true };
interface Product {
id: number;
name: string;
}
const ProductIDBrand: unique symbol = Symbol();
type ProductID = number & { readonly [ProductIDBrand]: true };
// Funkcija, tikinti UserID
function getUser(id: UserID): User {
// ... įgyvendinimas, kaip gauti vartotoją pagal ID
return {id: id, name: "Example User"};
}
const userID = 123 as UserID;
const productID = 456 as ProductID;
const user = getUser(userID);
// Tai sukeltų klaidą, jei būtų atkomentuota
// const user2 = getUser(productID); // Argumentas tipo 'ProductID' nėra priskiriamas parametrui tipo 'UserID'.
console.log(user);
Tai apsaugo nuo atsitiktinio `ProductID` perdavimo ten, kur tikimasi `UserID`, nors abu galiausiai yra reprezentuojami kaip skaičiai.
Darbas su bibliotekomis ir išoriniais tipais
Dirbant su išorinėmis bibliotekomis ar API, kurios neteikia paženklintų tipų, galite naudoti tipų tvirtinimus, kad sukurtumėte paženklintus tipus iš esamų reikšmių. Tačiau darydami tai būkite atsargūs, nes iš esmės tvirtinate, kad reikšmė atitinka paženklintą tipą, ir turite užtikrinti, kad taip yra iš tikrųjų.
// Tarkime, gaunate skaičių iš API, kuris atstovauja UserID
const rawUserID = 789; // Skaičius iš išorinio šaltinio
// Sukurkite paženklintą UserID iš neapdoroto skaičiaus
const userIDFromAPI = rawUserID as UserID;
Vykdymo laiko svarstymai
Svarbu prisiminti, kad nominalus ženklinimas „TypeScript“ yra tik kompiliavimo laiko konstruktas. Prekių ženklai (unikalūs simboliai) yra ištrinami kompiliavimo metu, todėl nėra jokios vykdymo laiko pridėtinės naštos. Tačiau tai taip pat reiškia, kad negalite pasikliauti prekių ženklais vykdymo laiko tipų tikrinimui. Jei jums reikia vykdymo laiko tipų tikrinimo, turėsite įgyvendinti papildomus mechanizmus, tokius kaip pasirinktinės tipų apsaugos (type guards).
Tipų apsaugos vykdymo laiko patvirtinimui
Norėdami atlikti paženklintų tipų patvirtinimą vykdymo metu, galite sukurti pasirinktines tipų apsaugas:
function isKilogram(value: number): value is Kilogram {
// Realiame scenarijuje čia galėtumėte pridėti papildomų patikrinimų,
// pavyzdžiui, užtikrinti, kad reikšmė yra tinkamame diapazone kilogramams.
return typeof value === 'number';
}
const someValue: any = 15;
if (isKilogram(someValue)) {
const kg: Kilogram = someValue;
console.log("Reikšmė yra Kilogram:", kg);
} else {
console.log("Reikšmė nėra Kilogram");
}
Tai leidžia saugiai susiaurinti reikšmės tipą vykdymo metu, užtikrinant, kad ji atitinka paženklintą tipą prieš jį naudojant.
Nominalaus ženklinimo privalumai
- Padidintas tipų saugumas: Užkerta kelią nenumatytiems tipų pakeitimams ir sumažina loginių klaidų riziką.
- Pagerintas kodo aiškumas: Padaro kodą skaitomesnį ir lengviau suprantamą, aiškiai atskiriant skirtingus tipus su ta pačia pagrindine reprezentacija.
- Sumažintas derinimo laikas: Sugaudomos su tipais susijusios klaidos kompiliavimo metu, taip taupant laiką ir pastangas derinimo metu.
- Didesnis pasitikėjimas kodu: Suteikia didesnį pasitikėjimą kodo teisingumu, taikant griežtesnius tipų apribojimus.
Nominalaus ženklinimo apribojimai
- Tik kompiliavimo metu: Prekių ženklai ištrinami kompiliavimo metu, todėl jie neužtikrina vykdymo laiko tipų tikrinimo.
- Reikalingi tipų tvirtinimai: Paženklintų tipų kūrimas dažnai reikalauja tipų tvirtinimų, kurie, neteisingai naudojami, gali apeiti tipų tikrinimą.
- Padidėjęs kodo šabloniškumas: Paženklintų tipų apibrėžimas ir naudojimas gali pridėti šiek tiek šabloniško kodo, nors tai galima sušvelninti naudojant bendrinius pagalbinius tipus.
Geriausios praktikos naudojant nominalius prekių ženklus
- Naudokite bendrinį ženklinimą: Kurkite bendrinius pagalbinius tipus, kad sumažintumėte šabloniškumą ir užtikrintumėte nuoseklumą.
- Naudokite tipų apsaugas: Prireikus, įgyvendinkite pasirinktines tipų apsaugas vykdymo laiko patvirtinimui.
- Taikykite prekių ženklus apdairiai: Nenaudokite nominalaus ženklinimo per daug. Taikykite jį tik tada, kai reikia griežtesnio tipų tikrinimo, siekiant išvengti loginių klaidų.
- Aiškiai dokumentuokite prekių ženklus: Aiškiai dokumentuokite kiekvieno paženklinto tipo paskirtį ir naudojimą.
- Atsižvelkite į našumą: Nors vykdymo laiko sąnaudos yra minimalios, kompiliavimo laikas gali padidėti dėl pernelyg didelio naudojimo. Prireikus, profiliuokite ir optimizuokite.
Pavyzdžiai įvairiose pramonės šakose ir programose
Nominalus ženklinimas pritaikomas įvairiose srityse:
- Finansinės sistemos: Skirtingų valiutų (USD, EUR, GBP) ir sąskaitų tipų (taupomoji, einamoji) atskyrimas, siekiant išvengti neteisingų operacijų ir skaičiavimų. Pavyzdžiui, bankinė programa gali naudoti nominalius tipus, siekdama užtikrinti, kad palūkanų skaičiavimai būtų atliekami tik taupomosioms sąskaitoms ir kad valiutų konvertavimas būtų taikomas teisingai pervedant lėšas tarp sąskaitų skirtingomis valiutomis.
- Elektroninės prekybos platformos: Produktų ID, klientų ID ir užsakymų ID atskyrimas, siekiant išvengti duomenų sugadinimo ir saugumo pažeidžiamumų. Įsivaizduokite, kad atsitiktinai priskiriate kliento kredito kortelės informaciją produktui – nominalūs tipai gali padėti išvengti tokių katastrofiškų klaidų.
- Sveikatos priežiūros programos: Pacientų ID, gydytojų ID ir vizitų ID atskyrimas, siekiant užtikrinti teisingą duomenų susiejimą ir išvengti atsitiktinio pacientų įrašų sumaišymo. Tai labai svarbu norint išlaikyti pacientų privatumą ir duomenų vientisumą.
- Tiekimo grandinės valdymas: Sandėlių ID, siuntų ID ir produktų ID atskyrimas, siekiant tiksliai sekti prekes ir išvengti logistikos klaidų. Pavyzdžiui, užtikrinant, kad siunta būtų pristatyta į teisingą sandėlį ir kad siuntoje esantys produktai atitiktų užsakymą.
- IoT (daiktų interneto) sistemos: Jutiklių ID, įrenginių ID ir vartotojų ID atskyrimas, siekiant užtikrinti tinkamą duomenų rinkimą ir valdymą. Tai ypač svarbu scenarijuose, kur saugumas ir patikimumas yra svarbiausi, pavyzdžiui, išmaniųjų namų automatizavimo ar pramonės valdymo sistemose.
- Žaidimai: Ginklų ID, personažų ID ir daiktų ID atskyrimas, siekiant pagerinti žaidimo logiką ir išvengti išnaudojimo. Paprasta klaida galėtų leisti žaidėjui apsirūpinti daiktu, skirtu tik NPC, sutrikdant žaidimo balansą.
Alternatyvos nominaliam ženklinimui
Nors nominalus ženklinimas yra galinga technika, tam tikrose situacijose panašių rezultatų galima pasiekti ir kitais būdais:
- Klasės: Klasių su privačiomis savybėmis naudojimas gali suteikti tam tikrą nominalaus tipizavimo laipsnį, nes skirtingų klasių egzemplioriai yra iš prigimties skirtingi. Tačiau šis metodas gali būti išsamesnis nei nominalus ženklinimas ir ne visada tinkamas.
- Enum: „TypeScript“ `enum` naudojimas suteikia tam tikrą nominalaus tipizavimo laipsnį vykdymo metu konkrečiam, ribotam galimų reikšmių rinkiniui.
- Literalūs tipai: Eilučių ar skaičių literalų tipų naudojimas gali apriboti galimas kintamojo reikšmes, tačiau šis metodas neužtikrina tokio paties tipų saugumo lygio kaip nominalus ženklinimas.
- Išorinės bibliotekos: Tokios bibliotekos kaip `io-ts` siūlo vykdymo laiko tipų tikrinimo ir patvirtinimo galimybes, kurias galima naudoti griežtesniems tipų apribojimams taikyti. Tačiau šios bibliotekos prideda vykdymo laiko priklausomybę ir ne visada yra būtinos.
Išvada
„TypeScript“ nominalus ženklinimas suteikia galingą būdą padidinti tipų saugumą ir išvengti loginių klaidų, sukuriant nepermatomus tipų apibrėžimus. Nors tai nėra tikro nominalaus tipizavimo pakaitalas, tai yra praktiškas sprendimas, galintis žymiai pagerinti jūsų „TypeScript“ kodo tvirtumą ir palaikomumą. Suprasdami nominalaus ženklinimo principus ir taikydami jį apdairiai, galite rašyti patikimesnes ir be klaidų veikiančias programas.
Sprendžiant, ar naudoti nominalų ženklinimą savo projektuose, nepamirškite atsižvelgti į kompromisus tarp tipų saugumo, kodo sudėtingumo ir vykdymo laiko pridėtinės naštos.
Įtraukdami geriausias praktikas ir atidžiai apsvarstydami alternatyvas, galite pasinaudoti nominaliu ženklinimu, kad parašytumėte švaresnį, lengviau prižiūrimą ir tvirtesnį „TypeScript“ kodą. Pasinaudokite tipų saugumo galia ir kurkite geresnę programinę įrangą!