Išsamus „TypeScript“ bendrinių tipų vadovas: sintaksė, privalumai, pažangus naudojimas ir geriausios praktikos tvarkant sudėtingus duomenų tipus.
TypeScript bendriniai tipai: kaip įvaldyti sudėtingus duomenų tipus tvirtoms programoms kurti
„TypeScript“, „JavaScript“ virššūkis, suteikia programuotojams galimybę rašyti tvirtesnį ir lengviau prižiūrimą kodą naudojant statinį tipizavimą. Viena iš galingiausių jo funkcijų yra bendriniai tipai (generics), kurie leidžia rašyti kodą, galintį veikti su įvairiais duomenų tipais, išlaikant tipų saugumą. Šiame vadove pateikiamas išsamus „TypeScript“ bendrinių tipų tyrimas, daugiausia dėmesio skiriant jų taikymui sudėtingiems duomenų tipams pasaulinės programinės įrangos kūrimo kontekste.
Kas yra bendriniai tipai?
Bendriniai tipai suteikia būdą rašyti pakartotinai naudojamą kodą, kuris gali veikti su skirtingais tipais. Užuot rašę atskiras funkcijas ar klases kiekvienam tipui, kurį norite palaikyti, galite parašyti vieną funkciją ar klasę, kuri naudoja tipų parametrus. Šie tipų parametrai yra rezervuotos vietos faktiniams tipams, kurie bus naudojami, kai funkcija ar klasė bus iškviesta ar sukurta. Tai ypač naudinga dirbant su sudėtingomis duomenų struktūromis, kuriose duomenų tipas gali skirtis.
Bendrinių tipų naudojimo privalumai
- Kodo pakartotinis naudojimas: Rašykite kodą vieną kartą ir naudokite jį su skirtingais tipais. Tai sumažina kodo dubliavimąsi ir padaro jūsų kodo bazę lengviau prižiūrimą.
- Tipų saugumas: Bendriniai tipai leidžia „TypeScript“ kompiliatoriui užtikrinti tipų saugumą kompiliavimo metu. Tai padeda išvengti vykdymo laiko klaidų, susijusių su tipų neatitikimais.
- Geresnis skaitomumas: Bendriniai tipai padaro jūsų kodą skaitomesnį, aiškiai nurodydami, su kokiais tipais jūsų funkcijos ir klasės yra sukurtos dirbti.
- Padidintas našumas: Kai kuriais atvejais bendriniai tipai gali pagerinti našumą, nes kompiliatorius gali optimizuoti sugeneruotą kodą, atsižvelgdamas į naudojamus konkrečius tipus.
Pagrindinė bendrinių tipų sintaksė
Pagrindinė bendrinių tipų sintaksė apima kampinių skliaustų (< >) naudojimą tipų parametrams deklaruoti. Šie tipų parametrai paprastai vadinami T, K, V ir pan., tačiau galite naudoti bet kokį galiojantį identifikatorių. Štai paprastas bendrinės funkcijos pavyzdys:
function identity<T>(arg: T): T {
return arg;
}
let myString: string = identity<string>("hello");
let myNumber: number = identity<number>(123);
let myBoolean: boolean = identity<boolean>(true);
console.log(myString); // Išvestis: hello
console.log(myNumber); // Išvestis: 123
console.log(myBoolean); // Išvestis: true
Šiame pavyzdyje <T> deklaruoja tipo parametrą, pavadintą T. Funkcija identity priima T tipo argumentą ir grąžina T tipo reikšmę. Iškviečiant funkciją, galite aiškiai nurodyti tipo parametrą (pvz., identity<string>) arba leisti „TypeScript“ jį nustatyti pagal argumento tipą.
Darbas su sudėtingais duomenų tipais
Bendriniai tipai tampa ypač vertingi dirbant su sudėtingais duomenų tipais, tokiais kaip masyvai, objektai ir sąsajos. Panagrinėkime keletą įprastų scenarijų:
Bendriniai masyvai
Galite naudoti bendrinius tipus kurdami funkcijas ar klases, kurios veikia su skirtingų tipų masyvais:
function arrayToString<T>(arr: T[]): string {
return arr.join(", ");
}
let numberArray: number[] = [1, 2, 3, 4, 5];
let stringArray: string[] = ["apple", "banana", "cherry"];
console.log(arrayToString(numberArray)); // Išvestis: 1, 2, 3, 4, 5
console.log(arrayToString(stringArray)); // Išvestis: apple, banana, cherry
Čia funkcija arrayToString priima T[] tipo masyvą ir grąžina masyvo eilutės reprezentaciją. Ši funkcija veikia su bet kokio tipo masyvais, todėl ji yra labai universali.
Bendriniai objektai
Bendriniai tipai taip pat gali būti naudojami apibrėžti funkcijas ar klases, kurios veikia su skirtingų formų objektais:
interface Person {
name: string;
age: number;
country: string; // Pridėta šalis pasauliniam kontekstui
}
interface Product {
id: number;
name: string;
price: number;
currency: string; // Pridėta valiuta pasauliniam kontekstui
}
function displayInfo<T extends { name: string }>(item: T): void {
console.log(`Vardas: ${item.name}`);
}
let person: Person = { name: "Alice", age: 30, country: "USA" };
let product: Product = { id: 1, name: "Laptop", price: 1200, currency: "USD" };
displayInfo(person); // Išvestis: Vardas: Alice
displayInfo(product); // Išvestis: Vardas: Laptop
Šiame pavyzdyje funkcija displayInfo priima T tipo objektą, kuris privalo turėti name savybę, kurios tipas yra eilutė (string). Sąlyga extends { name: string } yra apribojimas, kuris nurodo minimalius reikalavimus tipo parametrui T. Tai užtikrina, kad funkcija gali saugiai pasiekti name savybę.
Pažangus bendrinių tipų naudojimas
„TypeScript“ bendriniai tipai siūlo pažangesnes funkcijas, kurios leidžia kurti dar lankstesnį ir galingesnį kodą. Panagrinėkime keletą iš šių funkcijų:
Kelių tipų parametrai
Galite apibrėžti funkcijas ar klases su keliais tipų parametrais:
function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
interface Name {
firstName: string;
}
interface Age {
age: number;
}
const person: Name = { firstName: "Bob" };
const details: Age = { age: 42 };
const merged = merge(person, details);
console.log(merged.firstName); // Išvestis: Bob
console.log(merged.age); // Išvestis: 42
Funkcija merge priima du objektus, kurių tipai yra T ir U, ir grąžina naują objektą, kuriame yra abiejų objektų savybės. Tai yra galingas būdas sujungti duomenis iš skirtingų šaltinių.
Bendrinių tipų apribojimai
Kaip parodyta anksčiau, apribojimai leidžia apriboti tipus, kurie gali būti naudojami su bendriniu tipo parametru. Tai užtikrina, kad bendrinis kodas gali saugiai veikti su nurodytais tipais.
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity([1, 2, 3]); // Išvestis: 3
loggingIdentity("hello"); // Išvestis: 5
// loggingIdentity(123); // Klaida: argumento tipas 'number' nepriskiriamas parametro tipui 'Lengthwise'.
Funkcija loggingIdentity priima T tipo argumentą, kuris privalo turėti length savybę, kurios tipas yra skaičius (number). Tai užtikrina, kad funkcija gali saugiai pasiekti length savybę.
Bendrinės klasės
Bendriniai tipai taip pat gali būti naudojami su klasėmis:
class DataStorage<T> {
private data: T[] = [];
addItem(item: T) {
this.data.push(item);
}
removeItem(item: T) {
this.data = this.data.filter(d => d !== item);
}
getItems(): T[] {
return [...this.data];
}
}
const textStorage = new DataStorage<string>();
textStorage.addItem("apple");
textStorage.addItem("banana");
textStorage.removeItem("apple");
console.log(textStorage.getItems()); // Išvestis: [ 'banana' ]
const numberStorage = new DataStorage<number>();
numberStorage.addItem(1);
numberStorage.addItem(2);
numberStorage.removeItem(1);
console.log(numberStorage.getItems()); // Išvestis: [ 2 ]
Klasė DataStorage gali saugoti bet kokio tipo T duomenis. Tai leidžia kurti pakartotinai naudojamas duomenų struktūras, kurios yra tipų saugios.
Bendrinės sąsajos
Bendrinės sąsajos yra naudingos apibrėžiant kontraktus, kurie gali veikti su skirtingais tipais. Pavyzdžiui:
interface Result<T, E> {
success: boolean;
data?: T;
error?: E;
}
interface User {
id: number;
username: string;
email: string;
}
interface ErrorMessage {
code: number;
message: string;
}
function fetchUser(id: number): Result<User, ErrorMessage> {
if (id === 1) {
return { success: true, data: { id: 1, username: "john.doe", email: "john.doe@example.com" } };
} else {
return { success: false, error: { code: 404, message: "Vartotojas nerastas" } };
}
}
const userResult = fetchUser(1);
if (userResult.success) {
console.log(userResult.data.username);
} else {
console.log(userResult.error.message);
}
Sąsaja Result apibrėžia bendrinę struktūrą, skirtą operacijos rezultatui pavaizduoti. Ji gali turėti arba T tipo duomenis, arba E tipo klaidą. Tai yra įprastas modelis tvarkant asinchronines operacijas arba operacijas, kurios gali nepavykti.
Pagalbiniai tipai ir bendriniai tipai
„TypeScript“ suteikia keletą integruotų pagalbinių tipų, kurie gerai veikia su bendriniais tipais. Šie pagalbiniai tipai gali padėti galingai transformuoti ir manipuliuoti tipais.
Partial<T>
Partial<T> padaro visas T tipo savybes neprivalomomis:
interface Person {
name: string;
age: number;
}
type PartialPerson = Partial<Person>;
const partialPerson: PartialPerson = { name: "Alice" }; // Leidžiama
Readonly<T>
Readonly<T> padaro visas T tipo savybes tik skaitomomis:
interface Person {
name: string;
age: number;
}
type ReadonlyPerson = Readonly<Person>;
const readonlyPerson: ReadonlyPerson = { name: "Bob", age: 42 };
// readonlyPerson.age = 43; // Klaida: Negalima priskirti reikšmės 'age', nes tai tik skaitoma savybė.
Pick<T, K>
Pick<T, K> parenka savybių rinkinį K iš tipo T:
interface Person {
name: string;
age: number;
email: string;
}
type NameAndAge = Pick<Person, "name" | "age">;
const nameAndAge: NameAndAge = { name: "Charlie", age: 28 };
Omit<T, K>
Omit<T, K> pašalina savybių rinkinį K iš tipo T:
interface Person {
name: string;
age: number;
email: string;
}
type PersonWithoutEmail = Omit<Person, "email">;
const personWithoutEmail: PersonWithoutEmail = { name: "David", age: 35 };
Record<K, T>
Record<K, T> sukuria tipą su raktais K ir T tipo reikšmėmis:
type CountryCodes = "US" | "CA" | "UK" | "DE" | "FR" | "JP" | "CN" | "IN" | "BR" | "AU"; // Išplėstas sąrašas pasauliniam kontekstui
type Currency = "USD" | "CAD" | "GBP" | "EUR" | "JPY" | "CNY" | "INR" | "BRL" | "AUD"; // Išplėstas sąrašas pasauliniam kontekstui
type CurrencyMap = Record<CountryCodes, Currency>;
const currencyMap: CurrencyMap = {
"US": "USD",
"CA": "CAD",
"UK": "GBP",
"DE": "EUR",
"FR": "EUR",
"JP": "JPY",
"CN": "CNY",
"IN": "INR",
"BR": "BRL",
"AU": "AUD",
};
Atvaizduoti tipai
Atvaizduoti tipai leidžia transformuoti esamus tipus, iteruojant per jų savybes. Tai galingas būdas kurti naujus tipus remiantis esamais. Pavyzdžiui, galite sukurti tipą, kuris visas kito tipo savybes padaro tik skaitomomis:
interface Person {
name: string;
age: number;
}
type ReadonlyPerson = {
readonly [K in keyof Person]: Person[K];
};
const readonlyPerson: ReadonlyPerson = { name: "Eve", age: 25 };
// readonlyPerson.age = 26; // Klaida: Negalima priskirti reikšmės 'age', nes tai tik skaitoma savybė.
Šiame pavyzdyje [K in keyof Person] iteruoja per visus Person sąsajos raktus, o Person[K] pasiekia kiekvienos savybės tipą. Raktinis žodis readonly padaro kiekvieną savybę tik skaitoma.
Sąlyginiai tipai
Sąlyginiai tipai leidžia apibrėžti tipus remiantis sąlygomis. Tai galingas būdas kurti tipus, kurie prisitaiko prie skirtingų scenarijų.
type NonNullable<T> = T extends null | undefined ? never : T;
type MaybeString = string | null | undefined;
type StringType = NonNullable<MaybeString>; // string
function getValue<T>(value: T): NonNullable<T> {
if (value == null) { // Apdoroja ir null, ir undefined
throw new Error("Reikšmė negali būti null arba undefined");
}
return value as NonNullable<T>;
}
try {
const validValue = getValue("hello");
console.log(validValue.toUpperCase()); // Išvestis: HELLO
const invalidValue = getValue(null); // Tai sukels klaidą
console.log(invalidValue); // Ši eilutė nebus pasiekta
} catch (error: any) {
console.error(error.message); // Išvestis: Reikšmė negali būti null arba undefined
}
Šiame pavyzdyje tipas NonNullable<T> tikrina, ar T yra null arba undefined. Jei taip, jis grąžina never, o tai reiškia, kad tipas neleidžiamas. Priešingu atveju jis grąžina T. Tai leidžia kurti tipus, kurie garantuotai nebus nulinės reikšmės (non-nullable).
Geriausios bendrinių tipų naudojimo praktikos
Štai keletas geriausių praktikų, kurias reikia turėti omenyje naudojant bendrinius tipus:
- Naudokite aprašomuosius tipų parametrų pavadinimus: Pasirinkite pavadinimus, kurie aiškiai nurodo tipo parametro paskirtį.
- Naudokite apribojimus, kad apribotumėte tipus, kurie gali būti naudojami su bendriniu tipo parametru: Tai užtikrina, kad jūsų bendrinis kodas gali saugiai veikti su nurodytais tipais.
- Išlaikykite savo bendrinį kodą paprastą ir koncentruotą: Venkite per daug komplikuoti savo bendrinį kodą su per daug tipų parametrų ar sudėtingų apribojimų.
- Išsamiai dokumentuokite savo bendrinį kodą: Paaiškinkite tipų parametrų paskirtį ir visus naudojamus apribojimus.
- Apsvarstykite kompromisus tarp kodo pakartotinio naudojimo ir tipų saugumo: Nors bendriniai tipai gali pagerinti kodo pakartotinį naudojimą, jie taip pat gali padaryti jūsų kodą sudėtingesnį. Prieš naudodami bendrinius tipus, pasverkite privalumus ir trūkumus.
- Atsižvelkite į lokalizaciją ir globalizaciją (l10n ir g11n): Dirbdami su duomenimis, kurie turi būti rodomi skirtingų regionų vartotojams, užtikrinkite, kad jūsų bendriniai tipai palaikytų tinkamą formatavimą ir kultūrines konvencijas. Pavyzdžiui, skaičių ir datos formatavimas gali labai skirtis priklausomai nuo lokalės.
Pavyzdžiai pasauliniame kontekste
Panagrinėkime keletą pavyzdžių, kaip bendriniai tipai gali būti naudojami pasauliniame kontekste:
Valiutos konvertavimas
interface ConversionRate {
rate: number;
fromCurrency: string;
toCurrency: string;
}
function convertCurrency<T extends ConversionRate>(amount: number, rate: T): number {
return amount * rate.rate;
}
const usdToEurRate: ConversionRate = { rate: 0.85, fromCurrency: "USD", toCurrency: "EUR" };
const amountInUSD = 100;
const amountInEUR = convertCurrency(amountInUSD, usdToEurRate);
console.log(`${amountInUSD} USD yra lygu ${amountInEUR} EUR`); // Išvestis: 100 USD yra lygu 85 EUR
Datos formatavimas
interface DateFormatOptions {
locale: string;
options: Intl.DateTimeFormatOptions;
}
function formatDate<T extends DateFormatOptions>(date: Date, format: T): string {
return date.toLocaleDateString(format.locale, format.options);
}
const currentDate = new Date();
const usDateFormat: DateFormatOptions = { locale: "en-US", options: { year: 'numeric', month: 'long', day: 'numeric' } };
const germanDateFormat: DateFormatOptions = { locale: "de-DE", options: { year: 'numeric', month: 'long', day: 'numeric' } };
const japaneseDateFormat: DateFormatOptions = { locale: "ja-JP", options: { year: 'numeric', month: 'long', day: 'numeric' } };
console.log("JAV data: " + formatDate(currentDate, usDateFormat));
console.log("Vokietijos data: " + formatDate(currentDate, germanDateFormat));
console.log("Japonijos data: " + formatDate(currentDate, japaneseDateFormat));
Vertimo paslauga
interface Translation {
[key: string]: string; // Leidžia naudoti dinaminius kalbos raktus
}
interface LanguageData<T extends Translation> {
languageCode: string;
translations: T;
}
const englishTranslations: Translation = {
"hello": "Hello",
"goodbye": "Goodbye",
"welcome": "Welcome to our website!"
};
const spanishTranslations: Translation = {
"hello": "Hola",
"goodbye": "Adiós",
"welcome": "¡Bienvenido a nuestro sitio web!"
};
const frenchTranslations: Translation = {
"hello": "Bonjour",
"goodbye": "Au revoir",
"welcome": "Bienvenue sur notre site web !"
};
const languageData: LanguageData<typeof englishTranslations>[] = [
{languageCode: "en", translations: englishTranslations },
{languageCode: "es", translations: spanishTranslations },
{languageCode: "fr", translations: frenchTranslations}
];
function translate<T extends Translation>(key: string, languageCode: string, languageData: LanguageData<T>[]): string {
const lang = languageData.find(lang => lang.languageCode === languageCode);
if (!lang) {
return `Vertimas raktui ${key} kalboje ${languageCode} nerastas.`;
}
return lang.translations[key] || `Vertimas raktui ${key} nerastas.`;
}
console.log(translate("hello", "en", languageData)); // Išvestis: Hello
console.log(translate("hello", "es", languageData)); // Išvestis: Hola
console.log(translate("welcome", "fr", languageData)); // Išvestis: Bienvenue sur notre site web !
console.log(translate("missingKey", "de", languageData)); // Išvestis: Vertimas raktui missingKey kalboje de nerastas.
Išvada
„TypeScript“ bendriniai tipai yra galingas įrankis, skirtas rašyti pakartotinai naudojamą, tipų saugų kodą, kuris gali veikti su sudėtingais duomenų tipais. Suprasdami pagrindinę sintaksę, pažangias funkcijas ir geriausias bendrinių tipų praktikas, galite žymiai pagerinti savo „TypeScript“ programų kokybę ir palaikymą. Kuriant programas pasaulinei auditorijai, bendriniai tipai gali padėti tvarkyti įvairius duomenų formatus ir kultūrines konvencijas, užtikrinant sklandžią vartotojo patirtį visiems.