Avastage TypeScripti branded-tüübid – võimas tehnika nominaalse tüüpimise saavutamiseks struktuurses süsteemis. Õppige parandama tüübiohutust ja koodi selgust.
TypeScripti Branded-tüübid: Nominaalne tüüpimine struktuurses süsteemis
TypeScripti struktuurne tüübisüsteem pakub paindlikkust, kuid võib mõnikord põhjustada ootamatut käitumist. Branded-tüübid pakuvad võimalust rakendada nominaalset tüüpimist, parandades tüübiohutust ja koodi selgust. See artikkel uurib branded-tüüpe üksikasjalikult, pakkudes praktilisi näiteid ja parimaid praktikaid nende rakendamiseks.
Struktuurse vs. nominaalse tüüpimise mõistmine
Enne branded-tüüpidesse süvenemist selgitame struktuurse ja nominaalse tüüpimise erinevust.
Struktuurne tüüpimine (Duck Typing)
Struktuurses tüübisüsteemis peetakse kahte tüüpi ühilduvaks, kui neil on sama struktuur (st samad omadused samade tüüpidega). TypeScript kasutab struktuurset tüüpimist. Vaatleme seda näidet:
interface Point {
x: number;
y: number;
}
interface Vector {
x: number;
y: number;
}
const point: Point = { x: 10, y: 20 };
const vector: Vector = point; // Valid in TypeScript
console.log(vector.x); // Output: 10
Kuigi Point
ja Vector
on deklareeritud eraldi tüüpidena, lubab TypeScript määrata Point
-objekti Vector
-muutujale, kuna neil on sama struktuur. See võib olla mugav, kuid võib põhjustada ka vigu, kui on vaja eristada loogiliselt erinevaid tüüpe, millel on juhtumisi sama kuju. Näiteks laius- ja pikkuskraadide koordinaadid, mis võivad juhuslikult kattuda ekraani pikslite koordinaatidega.
Nominaalne tüüpimine
Nominaalses tüübisüsteemis peetakse tüüpe ühilduvaks ainult siis, kui neil on sama nimi. Isegi kui kahel tüübil on sama struktuur, käsitletakse neid eraldiseisvatena, kui neil on erinevad nimed. Keeled nagu Java ja C# kasutavad nominaalset tüüpimist.
Vajadus Branded-tüüpide järele
TypeScripti struktuurne tüüpimine võib olla problemaatiline, kui on vaja tagada, et väärtus kuulub kindlasse tüüpi, olenemata selle struktuurist. Näiteks valuutade esitamine. Teil võivad olla erinevad tüübid USD ja EUR jaoks, kuid mõlemad võiksid olla esitatud numbritena. Ilma mehhanismita nende eristamiseks võiksite kogemata teha tehteid vale valuutaga.
Branded-tüübid lahendavad selle probleemi, võimaldades luua eristatavaid tüüpe, mis on struktuurilt sarnased, kuid mida tüübisüsteem käsitleb erinevatena. See parandab tüübiohutust ja ennetab vigu, mis muidu võiksid läbi lipsata.
Branded-tüüpide rakendamine TypeScriptis
Branded-tüübid rakendatakse, kasutades ühisosa tüüpe (intersection types) ja unikaalset sümbolit või stringiliteraali. Idee on lisada tüübile "bränd", mis eristab seda teistest sama struktuuriga tüüpidest.
Sümbolite kasutamine (soovitatav)
Sümbolite kasutamine brändimiseks on üldiselt eelistatud, kuna sümbolid on garanteeritult unikaalsed.
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);
// Uncommenting the next line will cause a type error
// const invalidOperation = addUSD(usd1, eur1);
Selles näites on USD
ja EUR
branded-tüübid, mis põhinevad number
-tüübil. unique symbol
tagab, et need tüübid on eristatavad. Funktsioone createUSD
ja createEUR
kasutatakse nende tüüpide väärtuste loomiseks ning funktsioon addUSD
aktsepteerib ainult USD
väärtusi. Katse liita EUR
väärtus USD
väärtusele põhjustab tüübivea.
Stringiliteraalide kasutamine
Brändimiseks saab kasutada ka stringiliteraale, kuigi see lähenemine on vähem robustne kui sümbolite kasutamine, kuna stringiliteraalid ei ole garanteeritult unikaalsed.
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);
// Uncommenting the next line will cause a type error
// const invalidOperation = addUSD(usd1, eur1);
See näide saavutab sama tulemuse kui eelmine, kuid kasutab sümbolite asemel stringiliteraale. Kuigi see on lihtsam, on oluline tagada, et brändimiseks kasutatavad stringiliteraalid oleksid teie koodibaasis unikaalsed.
Praktilised näited ja kasutusjuhud
Branded-tüüpe saab rakendada erinevates stsenaariumides, kus on vaja tagada tüübiohutus väljaspool struktuurset ühilduvust.
ID-d
Kujutage ette süsteemi, kus on erinevat tüüpi ID-d, näiteks UserID
, ProductID
ja OrderID
. Kõik need ID-d võivad olla esitatud numbrite või stringidena, kuid te soovite vältida erinevat tüüpi ID-de juhuslikku segamist.
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 } {
// ... fetch user data
return { name: "Alice" };
}
function getProduct(id: ProductID): { name: string, price: number } {
// ... fetch product data
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);
// Uncommenting the next line will cause a type error
// const invalidCall = getUser(productID);
See näide demonstreerib, kuidas branded-tüübid võivad takistada ProductID
edastamist funktsioonile, mis ootab UserID
-d, parandades seeläbi tüübiohutust.
Domeenispetsiifilised väärtused
Branded-tüübid võivad olla kasulikud ka domeenispetsiifiliste piirangutega väärtuste esitamiseks. Näiteks võib teil olla tüüp protsentide jaoks, mis peaksid alati olema vahemikus 0 kuni 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('Percentage must be between 0 and 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);
// Uncommenting the next line will cause an error during runtime
// const invalidPercentage = createPercentage(120);
} catch (error) {
console.error(error);
}
See näide näitab, kuidas rakendada branded-tüübi väärtusele piirangut käitusajal. Kuigi tüübisüsteem ei saa garanteerida, et Percentage
väärtus on alati vahemikus 0 kuni 100, saab createPercentage
funktsioon selle piirangu käitusajal kehtestada. Branded-tüüpide käitusaegseks valideerimiseks võite kasutada ka teeke nagu io-ts.
Kuupäeva ja kellaaja esitused
Kuupäevade ja kellaaegadega töötamine võib olla keeruline erinevate formaatide ja ajavööndite tõttu. Branded-tüübid aitavad eristada erinevaid kuupäeva ja kellaaja esitusi.
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 {
// Validate that the date string is in UTC format (e.g., ISO 8601 with Z)
if (!/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(dateString)) {
throw new Error('Invalid UTC date format');
}
return dateString as UTCDate;
}
function createLocalDate(dateString: string): LocalDate {
// Validate that the date string is in local date format (e.g., YYYY-MM-DD)
if (!/\d{4}-\d{2}-\d{2}/.test(dateString)) {
throw new Error('Invalid local date format');
}
return dateString as LocalDate;
}
function convertUTCDateToLocalDate(utcDate: UTCDate): LocalDate {
// Perform time zone conversion
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);
}
See näide eristab UTC ja kohalikke kuupäevi, tagades, et töötate oma rakenduse erinevates osades õige kuupäeva ja kellaaja esitusega. Käitusaegne valideerimine tagab, et neile tüüpidele saab määrata ainult õigesti vormindatud kuupäevastringe.
Branded-tüüpide kasutamise parimad praktikad
Branded-tüüpide tõhusaks kasutamiseks TypeScriptis kaaluge järgmisi parimaid praktikaid:
- Kasutage brändimiseks sümboleid: Sümbolid pakuvad tugevaimat unikaalsuse garantiid, vähendades tüübivigade riski.
- Looge abifunktsioone: Kasutage abifunktsioone branded-tüüpi väärtuste loomiseks. See pakub keskset punkti valideerimiseks ja tagab järjepidevuse.
- Rakendage käitusaegset valideerimist: Kuigi branded-tüübid parandavad tüübiohutust, ei takista need valede väärtuste määramist käitusajal. Kasutage piirangute jõustamiseks käitusaegset valideerimist.
- Dokumenteerige Branded-tüübid: Dokumenteerige selgelt iga branded-tüübi eesmärk ja piirangud, et parandada koodi hooldatavust.
- Kaaluge jõudluse mõjusid: Branded-tüübid lisavad väikese lisakoormuse ühisosa tüübi ja abifunktsioonide vajaduse tõttu. Kaaluge jõudluse mõju oma koodi jõudluskriitilistes osades.
Branded-tüüpide eelised
- Parem tüübiohutus: Hoiab ära struktuurilt sarnaste, kuid loogiliselt erinevate tüüpide juhusliku segamise.
- Parem koodi selgus: Muudab koodi loetavamaks ja kergemini mõistetavaks, eristades tüüpe selgesõnaliselt.
- Vähem vigu: Püüab potentsiaalsed vead kinni kompileerimise ajal, vähendades käitusaegsete vigade riski.
- Suurenenud hooldatavus: Muudab koodi lihtsamini hooldatavaks ja refaktoreeritavaks, pakkudes selget ülesannete eraldamist.
Branded-tüüpide puudused
- Suurenenud keerukus: Lisab koodibaasi keerukust, eriti kui tegemist on paljude branded-tüüpidega.
- Käitusaegne lisakoormus: Lisab väikese käitusaegse lisakoormuse abifunktsioonide ja käitusaegse valideerimise vajaduse tõttu.
- Potentsiaalne korduvkood (boilerplate): Võib põhjustada korduvkoodi, eriti branded-tüüpide loomisel ja valideerimisel.
Alternatiivid Branded-tüüpidele
Kuigi branded-tüübid on võimas tehnika nominaalse tüüpimise saavutamiseks TypeScriptis, on ka alternatiivseid lähenemisviise, mida võiksite kaaluda.
Läbipaistmatud tüübid (Opaque Types)
Läbipaistmatud tüübid on sarnased branded-tüüpidele, kuid pakuvad selgesõnalisemat viisi aluseks oleva tüübi peitmiseks. TypeScriptil ei ole sisseehitatud tuge läbipaistmatutele tüüpidele, kuid neid saab simuleerida moodulite ja privaatsete sümbolite abil.
Klassid
Klasside kasutamine võib pakkuda objektorienteeritumat lähenemist eristatavate tüüpide defineerimisel. Kuigi klassid on TypeScriptis struktuurselt tüübitud, pakuvad nad selgemat ülesannete eraldamist ja neid saab kasutada piirangute jõustamiseks meetodite kaudu.
Teegid nagu `io-ts` või `zod`
Need teegid pakuvad keerukat käitusaegset tüübivalideerimist ja neid saab kombineerida branded-tüüpidega, et tagada nii kompileerimisaegne kui ka käitusaegne ohutus.
Kokkuvõte
TypeScripti branded-tüübid on väärtuslik tööriist tüübiohutuse ja koodi selguse parandamiseks struktuurses tüübisüsteemis. Lisades tüübile "brändi", saate rakendada nominaalset tüüpimist ja vältida struktuurilt sarnaste, kuid loogiliselt erinevate tüüpide juhuslikku segamist. Kuigi branded-tüübid lisavad mõningast keerukust ja lisakoormust, kaaluvad parema tüübiohutuse ja koodi hooldatavuse eelised sageli puudused üles. Kaaluge branded-tüüpide kasutamist stsenaariumides, kus peate tagama, et väärtus kuulub kindlasse tüüpi, olenemata selle struktuurist.
Mõistes struktuurse ja nominaalse tüüpimise põhimõtteid ning rakendades selles artiklis kirjeldatud parimaid praktikaid, saate branded-tüüpe tõhusalt kasutada, et kirjutada robustsemat ja hooldatavamat TypeScripti koodi. Alates valuutade ja ID-de esitamisest kuni domeenispetsiifiliste piirangute jõustamiseni pakuvad branded-tüübid paindlikku ja võimsat mehhanismi tüübiohutuse parandamiseks teie projektides.
TypeScriptiga töötades uurige erinevaid tehnikaid ja teeke, mis on saadaval tüübivalideerimiseks ja jõustamiseks. Kaaluge branded-tüüpide kasutamist koos käitusaegse valideerimise teekidega nagu io-ts
või zod
, et saavutada terviklik lähenemine tüübiohutusele.