Разгледайте разширените функции на TypeScript като типове литерни шаблони и условни типове, за да пишете по-изразителен и поддържан код. Овладейте манипулацията на типовете за сложни сценарии.
Разширени типове в TypeScript: Овладяване на типовете литерни шаблони и условни типове
Силата на TypeScript се крие в неговата мощна типова система. Докато основните типове като string, number и boolean са достатъчни за много сценарии, разширените функции като типове литерни шаблони и условни типове отварят ново ниво на изразителност и типова безопасност. Това ръководство предоставя изчерпателен преглед на тези разширени типове, изследвайки техните възможности и демонстрирайки практични приложения.
Разбиране на типовете литерни шаблони
Типовете литерни шаблони надграждат литерните шаблони на JavaScript, позволявайки ви да дефинирате типове въз основа на интерполация на низове. Това позволява създаването на типове, които представляват специфични низови модели, което прави вашия код по-стабилен и предсказуем.
Основен синтаксис и употреба
Типовете литерни шаблони използват обратни кавички (`), за да затворят дефиницията на типа, подобно на литерните шаблони на JavaScript. В рамките на обратните кавички можете да интерполирате други типове, използвайки синтаксиса ${}. Тук се случва магията – вие по същество създавате тип, който е низ, конструиран по време на компилация въз основа на типовете вътре в интерполацията.
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIEndpoint = `/api/${string}`;
// Пример за употреба
const getEndpoint: APIEndpoint = "/api/users"; // Валидно
const postEndpoint: APIEndpoint = "/api/products/123"; // Валидно
const invalidEndpoint: APIEndpoint = "/admin/settings"; // TypeScript няма да покаже грешка тук, тъй като `string` може да бъде всичко
В този пример APIEndpoint е тип, който представлява всеки низ, започващ с /api/. Въпреки че този основен пример е полезен, истинската сила на типовете литерни шаблони се появява, когато се комбинира с по-конкретни типови ограничения.
Комбиниране с типове съюзи
Типовете литерни шаблони наистина блестят, когато се използват с типове съюзи. Това ви позволява да създавате типове, които представляват конкретен набор от низови комбинации.
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIPath = "users" | "products" | "orders";
type APIEndpoint = `/${APIPath}/${HTTPMethod}`;
// Валидни API крайни точки
const getUsers: APIEndpoint = "/users/GET";
const postProducts: APIEndpoint = "/products/POST";
// Невалидни API крайни точки (ще доведат до грешки в TypeScript)
// const invalidEndpoint: APIEndpoint = "/users/PATCH"; // Грешка: "/users/PATCH" не може да се присвои на тип "/users/GET" | "/users/POST" | ... 3 more ... | "/orders/DELETE".
Сега APIEndpoint е по-ограничен тип, който позволява само специфични комбинации от API пътища и HTTP методи. TypeScript ще маркира всички опити за използване на невалидни комбинации, подобрявайки типовата безопасност.
Манипулация на низове с типове литерни шаблони
TypeScript предоставя вътрешни типове за манипулиране на низове, които работят безпроблемно с типове литерни шаблони. Тези типове ви позволяват да трансформирате низове по време на компилация.
- Големи букви: Преобразува низ в главни букви.
- Малки букви: Преобразува низ в малки букви.
- Капитализиране: Капитализира първата буква на низ.
- Декапитализиране: Декапитализира първата буква на низ.
type Greeting = "hello world";
type UppercaseGreeting = Uppercase; // "HELLO WORLD"
type LowercaseGreeting = Lowercase; // "hello world"
type CapitalizedGreeting = Capitalize; // "Hello world"
type UncapitalizedGreeting = Uncapitalize; // "hello world"
Тези типове за манипулиране на низове са особено полезни за автоматично генериране на типове въз основа на конвенции за именуване. Например, можете да извлечете типове действия от имена на събития или обратно.
Практически приложения на типовете литерни шаблони
- Дефиниция на API крайна точка: Както беше демонстрирано по-горе, дефиниране на API крайни точки с прецизни типови ограничения.
- Обработка на събития: Създаване на типове за имена на събития със специфични префикси и суфикси.
- CSS генериране на класове: Генериране на CSS имена на класове въз основа на имена и състояния на компоненти.
- Създаване на заявки към база данни: Осигуряване на типова безопасност при конструиране на заявки към база данни.
Международен пример: Форматиране на валута
Представете си, че изграждате финансово приложение, което поддържа множество валути. Можете да използвате типове литерни шаблони, за да наложите правилното форматиране на валутата.
type CurrencyCode = "USD" | "EUR" | "GBP" | "JPY";
type CurrencyFormat = `${number} ${T}`;
const priceUSD: CurrencyFormat<"USD"> = "100 USD"; // Валидно
const priceEUR: CurrencyFormat<"EUR"> = "50 EUR"; // Валидно
// const priceInvalid: CurrencyFormat<"USD"> = "100 EUR"; // Грешка: Тип 'string' не може да се присвои на тип '`${number} USD`'.
function formatCurrency(amount: number, currency: T): CurrencyFormat {
return `${amount} ${currency}`;
}
const formattedUSD = formatCurrency(250, "USD"); // Тип: "250 USD"
const formattedEUR = formatCurrency(100, "EUR"); // Тип: "100 EUR"
Този пример гарантира, че валутните стойности винаги са форматирани с правилния валутен код, предотвратявайки потенциални грешки.
Вникване в условните типове
Условните типове въвеждат логика за разклоняване в типовата система на TypeScript, позволявайки ви да дефинирате типове, които зависят от други типове. Тази функция е невероятно мощна за създаване на много гъвкави и използваеми повторно дефиниции на типове.
Основен синтаксис и употреба
Условните типове използват ключовата дума infer и тернарния оператор (condition ? trueType : falseType), за да дефинират типови условия.
type IsString = T extends string ? true : false;
type StringCheck = IsString; // type StringCheck = true
type NumberCheck = IsString; // type NumberCheck = false
В този пример IsString е условен тип, който проверява дали T може да се присвои на string. Ако е така, типът се разрешава на true; в противен случай се разрешава на false.
Ключовата дума infer
Ключовата дума infer ви позволява да извлечете тип от тип. Това е особено полезно при работа със сложни типове като типове функции или типове масиви.
type ReturnType any> = T extends (...args: any) => infer R ? R : any;
function add(a: number, b: number): number {
return a + b;
}
type AddReturnType = ReturnType; // type AddReturnType = number
В този пример ReturnType извлича типа на връщане на функционален тип T. Частта infer R от условния тип извежда типа на връщане и го присвоява на типовата променлива R. Ако T не е тип функция, типът се разрешава на any.
Дистрибутивни условни типове
Условните типове стават дистрибутивни, когато провереният тип е гол типови параметър. Това означава, че условният тип се прилага към всеки член на типовия съюз поотделно.
type ToArray = T extends any ? T[] : never;
type NumberOrStringArray = ToArray; // type NumberOrStringArray = string[] | number[]
В този пример ToArray преобразува тип T в тип масив. Тъй като T е гол типови параметър (не е обвит в друг тип), условният тип се прилага към number и string поотделно, което води до съюз от number[] и string[].
Практически приложения на условни типове
- Извличане на типове на връщане: Както беше демонстрирано по-горе, извличане на типа на връщане на функция.
- Филтриране на типове от съюз: Създаване на тип, който съдържа само специфични типове от съюз.
- Дефиниране на претоварени типове функции: Създаване на различни типове функции въз основа на входни типове.
- Създаване на типови защити: Дефиниране на функции, които стесняват типа на променлива.
Международен пример: Обработка на различни формати за дата
Различните региони на света използват различни формати за дата. Можете да използвате условни типове, за да обработите тези вариации.
type DateFormat = "YYYY-MM-DD" | "MM/DD/YYYY" | "DD.MM.YYYY";
type ParseDate = T extends "YYYY-MM-DD"
? { year: number; month: number; day: number; format: "YYYY-MM-DD" }
: T extends "MM/DD/YYYY"
? { month: number; day: number; year: number; format: "MM/DD/YYYY" }
: T extends "DD.MM.YYYY"
? { day: number; month: number; year: number; format: "DD.MM.YYYY" }
: never;
function parseDate(dateString: string, format: T): ParseDate {
// (Имплементацията ще обработва различни формати на дата)
if (format === "YYYY-MM-DD") {
const [year, month, day] = dateString.split("-").map(Number);
return { year, month, day, format } as ParseDate;
} else if (format === "MM/DD/YYYY") {
const [month, day, year] = dateString.split("/").map(Number);
return { month, day, year, format } as ParseDate;
} else if (format === "DD.MM.YYYY") {
const [day, month, year] = dateString.split(".").map(Number);
return { day, month, year, format } as ParseDate;
} else {
throw new Error("Invalid date format");
}
}
const parsedDateISO = parseDate("2023-10-27", "YYYY-MM-DD"); // Тип: { year: number; month: number; day: number; format: "YYYY-MM-DD"; }
const parsedDateUS = parseDate("10/27/2023", "MM/DD/YYYY"); // Тип: { month: number; day: number; year: number; format: "MM/DD/YYYY"; }
const parsedDateEU = parseDate("27.10.2023", "DD.MM.YYYY"); // Тип: { day: number; month: number; year: number; format: "DD.MM.YYYY"; }
console.log(parsedDateISO.year); // Достъп до годината, знаейки, че ще бъде там
Този пример използва условни типове, за да дефинира различни функции за анализ на дата въз основа на указания формат на дата. Типът ParseDate гарантира, че върнатият обект има правилните свойства въз основа на формата.
Комбиниране на литерни шаблони и условни типове
Истинската сила идва, когато комбинирате типове литерни шаблони и условни типове. Това позволява невероятно мощни манипулации на типове.
type EventName = `on${Capitalize}`;
type ExtractEventPayload = T extends EventName
? { type: T; payload: any } // Simplified for demonstration
: never;
type ClickEvent = EventName<"click">; // "onClick"
type MouseOverEvent = EventName<"mouseOver">; // "onMouseOver"
//Примерна функция, която приема тип
function processEvent(event: T): ExtractEventPayload {
//В реална реализация действително бихме изпратили събитието.
console.log(`Обработка на събитие ${event}`);
//В реална реализация полезният товар би се основавал на типа на събитието.
return { type: event, payload: {} } as ExtractEventPayload;
}
//Обърнете внимание, че типовете на връщане са много специфични:
const clickEvent = processEvent("onClick"); // { type: "onClick"; payload: any; }
const mouseOverEvent = processEvent("onMouseOver"); // { type: "onMouseOver"; payload: any; }
//Ако използвате други низове, получавате никога:
// const someOtherEvent = processEvent("someOtherEvent"); // Типът е `never`
Най-добри практики и съображения
- Поддържайте го просто: Докато са мощни, тези разширени типове могат бързо да станат сложни. Стремете се към яснота и поддръжка.
- Тествайте старателно: Уверете се, че вашите дефиниции на типове се държат както се очаква, като пишете изчерпателни модулни тестове.
- Документирайте своя код: Ясно документирайте целта и поведението на вашите разширени типове, за да подобрите четимостта на кода.
- Обмислете производителността: Прекомерната употреба на разширени типове може да повлияе на времето за компилация. Профилирайте своя код и оптимизирайте, където е необходимо.
Заключение
Типовете литерни шаблони и условните типове са мощни инструменти в арсенала на TypeScript. Като овладеете тези разширени типове, можете да пишете по-изразителен, поддържан и типово-безопасен код. Тези функции ви позволяват да уловите сложни взаимоотношения между типовете, да наложите по-строги ограничения и да създадете силно използваеми повторно дефиниции на типове. Прегърнете тези техники, за да повишите уменията си в TypeScript и да изградите стабилни и мащабируеми приложения за глобална аудитория.