Išnagrinėkite TypeScript „branded“ tipus – galingą metodą nominaliam tipavimui struktūrinėje tipų sistemoje. Sužinokite, kaip padidinti tipų saugumą ir kodo aiškumą.
TypeScript „Branded“ Tipai: Nominalusis Tipavimas Struktūrinėje Sistemoje
TypeScript struktūrinė tipų sistema suteikia lankstumo, tačiau kartais gali sukelti netikėtą elgesį. „Branded“ tipai suteikia būdą įgyvendinti nominalųjį tipavimą, taip padidinant tipų saugumą ir kodo aiškumą. Šiame straipsnyje išsamiai nagrinėjami „branded“ tipai, pateikiami praktiniai pavyzdžiai ir geriausios jų įgyvendinimo praktikos.
Struktūrinio ir Nominaliojo Tipavimo Supratimas
Prieš gilinantis į „branded“ tipus, išsiaiškinkime skirtumą tarp struktūrinio ir nominaliojo tipavimo.
Struktūrinis Tipavimas (Ančių Tipavimas)
Struktūrinėje tipų sistemoje du tipai laikomi suderinamais, jei jie turi tą pačią struktūrą (t. y. tas pačias savybes su tais pačiais tipais). TypeScript naudoja struktūrinį tipavimą. Panagrinėkime šį pavyzdį:
interface Point {
x: number;
y: number;
}
interface Vector {
x: number;
y: number;
}
const point: Point = { x: 10, y: 20 };
const vector: Vector = point; // Teisinga TypeScript kalboje
console.log(vector.x); // Išvestis: 10
Nors Point
ir Vector
yra deklaruoti kaip skirtingi tipai, TypeScript leidžia priskirti Point
objektą Vector
kintamajam, nes jie turi tą pačią struktūrą. Tai gali būti patogu, bet taip pat gali sukelti klaidų, jei reikia atskirti logiškai skirtingus tipus, kurie atsitiktinai turi tą pačią formą. Pavyzdžiui, galvojant apie platumos/ilgumos koordinates, kurios gali netyčia sutapti su ekrano pikselių koordinatėmis.
Nominalusis Tipavimas
Nominaliojoje tipų sistemoje tipai laikomi suderinamais tik tada, jei jie turi tą patį pavadinimą. Net jei du tipai turi tą pačią struktūrą, jie laikomi skirtingais, jei jų pavadinimai skiriasi. Tokios kalbos kaip Java ir C# naudoja nominalųjį tipavimą.
„Branded“ Tipų Poreikis
TypeScript struktūrinis tipavimas gali būti problemiškas, kai reikia užtikrinti, kad reikšmė priklauso konkrečiam tipui, nepriklausomai nuo jos struktūros. Pavyzdžiui, įsivaizduokite valiutų atvaizdavimą. Galite turėti skirtingus tipus USD ir EUR, tačiau abu jie gali būti atvaizduojami kaip skaičiai. Be mechanizmo, kuris juos atskirtų, galėtumėte netyčia atlikti operacijas su neteisinga valiuta.
„Branded“ tipai sprendžia šią problemą leisdami kurti skirtingus tipus, kurie yra struktūriškai panašūs, bet tipų sistemos traktuojami kaip skirtingi. Tai padidina tipų saugumą ir apsaugo nuo klaidų, kurios kitaip galėtų praslysti.
„Branded“ Tipų Įgyvendinimas TypeScript Kalboje
„Branded“ tipai įgyvendinami naudojant sankirtos tipus (intersection types) ir unikalų simbolį arba eilutės literalą. Idėja yra pridėti „ženklą“ (brand) prie tipo, kuris jį atskirtų nuo kitų tos pačios struktūros tipų.
Simbolių Naudojimas (Rekomenduojama)
Simbolių naudojimas ženklinimui yra labiau pageidautinas, nes simboliai garantuotai yra unikalūs.
const USD = Symbol('USD');
type USD = number & { readonly [USD]: unique symbol };
const EUR = Symbol('EUR');
type EUR = number & { readonly [EUR]: unique symbol };
function createUSD(value: number): USD {
return value as USD;
}
function createEUR(value: number): EUR {
return value as EUR;
}
function addUSD(a: USD, b: USD): USD {
return (a + b) as USD;
}
const usd1 = createUSD(10);
const usd2 = createUSD(20);
const eur1 = createEUR(15);
const totalUSD = addUSD(usd1, usd2);
console.log("Total USD:", totalUSD);
// Panaikinus šios eilutės komentarą, įvyks tipo klaida
// const invalidOperation = addUSD(usd1, eur1);
Šiame pavyzdyje USD
ir EUR
yra „branded“ tipai, pagrįsti number
tipu. unique symbol
užtikrina, kad šie tipai yra skirtingi. Funkcijos createUSD
ir createEUR
naudojamos kurti šių tipų reikšmes, o funkcija addUSD
priima tik USD
reikšmes. Bandymas pridėti EUR
reikšmę prie USD
reikšmės sukels tipo klaidą.
Eilutės Literalių Naudojimas
Ženklinimui taip pat galite naudoti eilutės literalus, nors šis metodas yra mažiau patikimas nei simbolių naudojimas, nes eilutės literalai nėra garantuotai unikalūs.
type USD = number & { readonly __brand: 'USD' };
type EUR = number & { readonly __brand: 'EUR' };
function createUSD(value: number): USD {
return value as USD;
}
function createEUR(value: number): EUR {
return value as EUR;
}
function addUSD(a: USD, b: USD): USD {
return (a + b) as USD;
}
const usd1 = createUSD(10);
const usd2 = createUSD(20);
const eur1 = createEUR(15);
const totalUSD = addUSD(usd1, usd2);
console.log("Total USD:", totalUSD);
// Panaikinus šios eilutės komentarą, įvyks tipo klaida
// const invalidOperation = addUSD(usd1, eur1);
Šis pavyzdys pasiekia tą patį rezultatą kaip ir ankstesnis, tačiau naudoja eilutės literalus vietoj simbolių. Nors tai paprasčiau, svarbu užtikrinti, kad ženklinimui naudojami eilutės literalai būtų unikalūs jūsų kodo bazėje.
Praktiniai Pavyzdžiai ir Panaudojimo Atvejai
„Branded“ tipus galima taikyti įvairiuose scenarijuose, kur reikia užtikrinti tipų saugumą, viršijantį struktūrinį suderinamumą.
ID
Įsivaizduokite sistemą su skirtingų tipų ID, pavyzdžiui, UserID
, ProductID
ir OrderID
. Visi šie ID gali būti atvaizduojami kaip skaičiai arba eilutės, tačiau norite išvengti atsitiktinio skirtingų ID tipų maišymo.
const UserIDBrand = Symbol('UserID');
type UserID = string & { readonly [UserIDBrand]: unique symbol };
const ProductIDBrand = Symbol('ProductID');
type ProductID = string & { readonly [ProductIDBrand]: unique symbol };
function getUser(id: UserID): { name: string } {
// ... gauti vartotojo duomenis
return { name: "Alice" };
}
function getProduct(id: ProductID): { name: string, price: number } {
// ... gauti produkto duomenis
return { name: "Example Product", price: 25 };
}
function createUserID(id: string): UserID {
return id as UserID;
}
function createProductID(id: string): ProductID {
return id as ProductID;
}
const userID = createUserID('user123');
const productID = createProductID('product456');
const user = getUser(userID);
const product = getProduct(productID);
console.log("User:", user);
console.log("Product:", product);
// Panaikinus šios eilutės komentarą, įvyks tipo klaida
// const invalidCall = getUser(productID);
Šis pavyzdys parodo, kaip „branded“ tipai gali užkirsti kelią ProductID
perdavimui funkcijai, kuri tikisi UserID
, taip padidinant tipų saugumą.
Domeno Specifinės Reikšmės
„Branded“ tipai taip pat gali būti naudingi atvaizduojant domeno specifines reikšmes su apribojimais. Pavyzdžiui, galite turėti tipą procentams, kurie visada turėtų būti tarp 0 ir 100.
const PercentageBrand = Symbol('Percentage');
type Percentage = number & { readonly [PercentageBrand]: unique symbol };
function createPercentage(value: number): Percentage {
if (value < 0 || value > 100) {
throw new Error('Procentinė išraiška turi būti tarp 0 ir 100');
}
return value as Percentage;
}
function applyDiscount(price: number, discount: Percentage): number {
return price * (1 - discount / 100);
}
try {
const discount = createPercentage(20);
const discountedPrice = applyDiscount(100, discount);
console.log("Discounted Price:", discountedPrice);
// Panaikinus šios eilutės komentarą, įvyks klaida vykdymo metu
// const invalidPercentage = createPercentage(120);
} catch (error) {
console.error(error);
}
Šis pavyzdys rodo, kaip vykdymo metu įgyvendinti apribojimą „branded“ tipo reikšmei. Nors tipų sistema negali garantuoti, kad Percentage
reikšmė visada bus tarp 0 ir 100, funkcija createPercentage
gali įgyvendinti šį apribojimą vykdymo metu. Taip pat galite naudoti bibliotekas, tokias kaip io-ts, kad įgyvendintumėte „branded“ tipų validavimą vykdymo metu.
Datos ir Laiko Atvaizdavimas
Darbas su datomis ir laikais gali būti sudėtingas dėl įvairių formatų ir laiko juostų. „Branded“ tipai gali padėti atskirti skirtingus datos ir laiko atvaizdavimus.
const UTCDateBrand = Symbol('UTCDate');
type UTCDate = string & { readonly [UTCDateBrand]: unique symbol };
const LocalDateBrand = Symbol('LocalDate');
type LocalDate = string & { readonly [LocalDateBrand]: unique symbol };
function createUTCDate(dateString: string): UTCDate {
// Patikrinti, ar datos eilutė yra UTC formatu (pvz., ISO 8601 su Z)
if (!/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(dateString)) {
throw new Error('Neteisingas UTC datos formatas');
}
return dateString as UTCDate;
}
function createLocalDate(dateString: string): LocalDate {
// Patikrinti, ar datos eilutė yra vietinės datos formatu (pvz., YYYY-MM-DD)
if (!/\d{4}-\d{2}-\d{2}/.test(dateString)) {
throw new Error('Neteisingas vietinės datos formatas');
}
return dateString as LocalDate;
}
function convertUTCDateToLocalDate(utcDate: UTCDate): LocalDate {
// Atlikti laiko juostos konvertavimą
const date = new Date(utcDate);
const localDateString = date.toLocaleDateString();
return createLocalDate(localDateString);
}
try {
const utcDate = createUTCDate('2024-01-20T10:00:00.000Z');
const localDate = convertUTCDateToLocalDate(utcDate);
console.log("UTC Date:", utcDate);
console.log("Local Date:", localDate);
} catch (error) {
console.error(error);
}
Šis pavyzdys atskiria UTC ir vietines datas, užtikrindamas, kad skirtingose programos dalyse dirbate su teisingu datos ir laiko atvaizdavimu. Vykdymo metu atliekamas patikrinimas užtikrina, kad tik teisingai suformatuotos datos eilutės gali būti priskirtos šiems tipams.
Geriausios „Branded“ Tipų Naudojimo Praktikos
Norėdami efektyviai naudoti „branded“ tipus TypeScript kalboje, atsižvelkite į šias geriausias praktikas:
- Naudokite Simbolius Ženklinimui: Simboliai suteikia stipriausią unikalumo garantiją, mažindami tipo klaidų riziką.
- Kurkite Pagalbines Funkcijas: Naudokite pagalbines funkcijas, kad sukurtumėte „branded“ tipų reikšmes. Tai suteikia centrinį validavimo tašką ir užtikrina nuoseklumą.
- Taikykite Vykdymo Metu Atliekamą Validavimą: Nors „branded“ tipai padidina tipų saugumą, jie neapsaugo nuo neteisingų reikšmių priskyrimo vykdymo metu. Naudokite validavimą vykdymo metu, kad įgyvendintumėte apribojimus.
- Dokumentuokite „Branded“ Tipus: Aiškiai dokumentuokite kiekvieno „branded“ tipo paskirtį ir apribojimus, kad pagerintumėte kodo palaikymą.
- Apsvarstykite Poveikį Našumui: „Branded“ tipai sukuria nedideles papildomas išlaidas dėl sankirtos tipo ir pagalbinių funkcijų poreikio. Apsvarstykite poveikį našumui kritinėse kodo dalyse.
„Branded“ Tipų Privalumai
- Padidintas Tipų Saugumas: Apsaugo nuo atsitiktinio struktūriškai panašių, bet logiškai skirtingų tipų maišymo.
- Pagerintas Kodo Aiškumas: Padaro kodą skaitomesnį ir lengviau suprantamą, aiškiai atskiriant tipus.
- Sumažintas Klaidų Skaičius: Sugaudžia galimas klaidas kompiliavimo metu, mažindamas vykdymo metu atsirandančių klaidų riziką.
- Padidintas Palaikomumas: Palengvina kodo palaikymą ir refaktorinimą, suteikdamas aiškų atsakomybių atskyrimą.
„Branded“ Tipų Trūkumai
- Padidėjęs Sudėtingumas: Prideda sudėtingumo kodo bazei, ypač kai dirbama su daugeliu „branded“ tipų.
- Vykdymo Meto Papildomos Išlaidos: Sukuria nedideles vykdymo metu atsirandančias išlaidas dėl pagalbinių funkcijų ir validavimo poreikio.
- Potencialus Šabloninio Kodo Kiekis: Gali lemti šabloninio (boilerplate) kodo atsiradimą, ypač kuriant ir validuojant „branded“ tipus.
Alternatyvos „Branded“ Tipams
Nors „branded“ tipai yra galingas metodas nominaliam tipavimui TypeScript kalboje pasiekti, yra ir alternatyvių požiūrių, kuriuos galite apsvarstyti.
Nepermatomi (Opaque) Tipai
Nepermatomi tipai yra panašūs į „branded“ tipus, bet suteikia aiškesnį būdą paslėpti pagrindinį tipą. TypeScript neturi integruoto nepermatomų tipų palaikymo, tačiau juos galima imituoti naudojant modulius ir privačius simbolius.
Klasės
Klasių naudojimas gali suteikti labiau į objektus orientuotą požiūrį į skirtingų tipų apibrėžimą. Nors TypeScript kalboje klasės yra struktūriškai tipuojamos, jos siūlo aiškesnį atsakomybių atskyrimą ir gali būti naudojamos apribojimams įgyvendinti per metodus.
Bibliotekos, tokios kaip io-ts
ar zod
Šios bibliotekos suteikia sudėtingą tipų validavimą vykdymo metu ir gali būti derinamos su „branded“ tipais, kad būtų užtikrintas tiek kompiliavimo, tiek vykdymo metu saugumas.
Išvada
TypeScript „branded“ tipai yra vertingas įrankis, didinantis tipų saugumą ir kodo aiškumą struktūrinėje tipų sistemoje. Pridėdami „ženklą“ prie tipo, galite įgyvendinti nominalųjį tipavimą ir išvengti atsitiktinio struktūriškai panašių, bet logiškai skirtingų tipų maišymo. Nors „branded“ tipai prideda šiek tiek sudėtingumo ir papildomų išlaidų, pagerinto tipų saugumo ir kodo palaikomumo nauda dažnai nusveria trūkumus. Apsvarstykite galimybę naudoti „branded“ tipus scenarijuose, kur reikia užtikrinti, kad reikšmė priklauso konkrečiam tipui, nepriklausomai nuo jo struktūros.
Suprasdami struktūrinio ir nominaliojo tipavimo principus bei taikydami šiame straipsnyje aprašytas geriausias praktikas, galėsite efektyviai išnaudoti „branded“ tipus rašydami patikimesnį ir lengviau palaikomą TypeScript kodą. Nuo valiutų ir ID atvaizdavimo iki domeno specifinių apribojimų įgyvendinimo, „branded“ tipai suteikia lankstų ir galingą mechanizmą tipų saugumui jūsų projektuose pagerinti.
Dirbdami su TypeScript, tyrinėkite įvairius metodus ir bibliotekas, skirtas tipų validavimui ir įgyvendinimui. Apsvarstykite galimybę naudoti „branded“ tipus kartu su vykdymo metu atliekamo validavimo bibliotekomis, tokiomis kaip io-ts
ar zod
, kad pasiektumėte visapusišką požiūrį į tipų saugumą.