Lietuvių

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:

„Branded“ Tipų Privalumai

„Branded“ Tipų Trūkumai

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ą.