Вивчіть розширені можливості TypeScript, як-от типи літералів шаблонів і умовні типи, щоб писати виразніший і зручніший для підтримки код. Опануйте маніпуляцію типами для складних сценаріїв.
Розширені типи TypeScript: Опанування типів літералів шаблонів і умовних типів
Сила TypeScript полягає в його потужній системі типів. У той час як базові типи, як-от string, number і boolean, є достатніми для багатьох сценаріїв, розширені можливості, як-от типи літералів шаблонів і умовні типи, відкривають новий рівень виразності та безпеки типів. Цей посібник містить вичерпний огляд цих розширених типів, досліджуючи їхні можливості та демонструючи практичні застосування.
Розуміння типів літералів шаблонів
Типи літералів шаблонів базуються на літералах шаблонів JavaScript, дозволяючи визначати типи на основі інтерполяції рядків. Це дозволяє створювати типи, які представляють певні рядкові шаблони, роблячи ваш код більш надійним і передбачуваним.
Основний синтаксис і використання
Типи літералів шаблонів використовують зворотні лапки (`) для позначення визначення типу, подібно до літералів шаблонів JavaScript. У межах зворотних лапок ви можете інтерполювати інші типи, використовуючи синтаксис ${}. Тут відбувається магія – ви, по суті, створюєте тип, який є рядком, побудованим під час компіляції на основі типів всередині інтерполяції.
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIEndpoint = `/api/${string}`;
// Example Usage
const getEndpoint: APIEndpoint = "/api/users"; // Valid
const postEndpoint: APIEndpoint = "/api/products/123"; // Valid
const invalidEndpoint: APIEndpoint = "/admin/settings"; // TypeScript will not show an error here as `string` can be anything
У цьому прикладі APIEndpoint – це тип, який представляє будь-який рядок, що починається з /api/. Хоча цей базовий приклад корисний, справжня сила типів літералів шаблонів проявляється в поєднанні з більш конкретними обмеженнями типу.
Об’єднання з типами об’єднань
Типи літералів шаблонів справді сяють, коли використовуються з типами об’єднань. Це дозволяє створювати типи, які представляють певний набір комбінацій рядків.
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIPath = "users" | "products" | "orders";
type APIEndpoint = `/${APIPath}/${HTTPMethod}`;
// Valid API Endpoints
const getUsers: APIEndpoint = "/users/GET";
const postProducts: APIEndpoint = "/products/POST";
// Invalid API Endpoints (will result in TypeScript errors)
// const invalidEndpoint: APIEndpoint = "/users/PATCH"; // Error: "/users/PATCH" is not assignable to type "/users/GET" | "/users/POST" | "/users/PUT" | "/users/DELETE" | "/products/GET" | "/products/POST" | ... 3 more ... | "/orders/DELETE".
Тепер APIEndpoint – це більш обмежений тип, який дозволяє лише певні комбінації шляхів API та методів HTTP. TypeScript позначатиме будь-які спроби використання недійсних комбінацій, покращуючи безпеку типів.
Маніпулювання рядками з типами літералів шаблонів
TypeScript надає внутрішні типи маніпулювання рядками, які бездоганно працюють з типами літералів шаблонів. Ці типи дозволяють перетворювати рядки під час компіляції.
- Uppercase: Перетворює рядок на верхній регістр.
- Lowercase: Перетворює рядок на нижній регістр.
- Capitalize: Перетворює перший символ рядка на верхній регістр.
- Uncapitalize: Перетворює перший символ рядка на нижній регістр.
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"; // Valid
const priceEUR: CurrencyFormat<"EUR"> = "50 EUR"; // Valid
// const priceInvalid: CurrencyFormat<"USD"> = "100 EUR"; // Error: Type 'string' is not assignable to type '`${number} USD`'.
function formatCurrency(amount: number, currency: T): CurrencyFormat {
return `${amount} ${currency}`;
}
const formattedUSD = formatCurrency(250, "USD"); // Type: "250 USD"
const formattedEUR = formatCurrency(100, "EUR"); // Type: "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 {
// (Implementation would handle different date formats)
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"); // Type: { year: number; month: number; day: number; format: "YYYY-MM-DD"; }
const parsedDateUS = parseDate("10/27/2023", "MM/DD/YYYY"); // Type: { month: number; day: number; year: number; format: "MM/DD/YYYY"; }
const parsedDateEU = parseDate("27.10.2023", "DD.MM.YYYY"); // Type: { day: number; month: number; year: number; format: "DD.MM.YYYY"; }
console.log(parsedDateISO.year); // Access the year knowing it will be there
У цьому прикладі використовуються умовні типи для визначення різних функцій розбору дати на основі вказаного формату дати. Тип 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"
//Example function that takes a type
function processEvent(event: T): ExtractEventPayload {
//In a real implementation, we would actually dispatch the event.
console.log(`Processing event ${event}`);
//In a real implementation, the payload would be based on event type.
return { type: event, payload: {} } as ExtractEventPayload;
}
//Note that the return types are very specific:
const clickEvent = processEvent("onClick"); // { type: "onClick"; payload: any; }
const mouseOverEvent = processEvent("onMouseOver"); // { type: "onMouseOver"; payload: any; }
//If you use other strings, you get never:
// const someOtherEvent = processEvent("someOtherEvent"); // Type is `never`
Найкращі практики та міркування
- Зберігайте простоту: Хоча ці розширені типи є потужними, вони можуть швидко ускладнитися. Прагніть до ясності та зручності обслуговування.
- Ретельно тестуйте: Переконайтеся, що ваші визначення типу поводяться відповідно до очікувань, написавши вичерпні юніт-тести.
- Документуйте свій код: Чітко документуйте призначення та поведінку своїх розширених типів, щоб покращити читабельність коду.
- Враховуйте продуктивність: Надмірне використання розширених типів може вплинути на час компіляції. Профілюйте свій код та оптимізуйте там, де це необхідно.
Висновок
Типи літералів шаблонів і умовні типи є потужними інструментами в арсеналі TypeScript. Освоївши ці розширені типи, ви можете писати більш виразний, зручний для підтримки та безпечний за типами код. Ці функції дозволяють фіксувати складні взаємозв’язки між типами, застосовувати суворіші обмеження та створювати визначення типів, що добре піддаються повторному використанню. Використовуйте ці методи, щоб підвищити свої навички TypeScript і створювати надійні та масштабовані програми для глобальної аудиторії.