TypeScript - Мapped Types: Пособие для начинающих
Здравствуйте, будущие маги TypeScript! Сегодня мы отправимся в увлекательное путешествие в мир Mapped Types. Не беспокойтесь, если вы новички в программировании - я буду вашим дружелюбным проводником, и мы будем двигаться шаг за шагом. К концу этого руководства вы будете mapping типы как профессионал!
Что такое Mapped Types?
Прежде чем мы углубимся, давайте поймем, что такое Mapped Types. Представьте, что у вас есть коробка с разнообразными конфетами, и вы хотите создать новую коробку, где каждая конфета завернута в золотую фольгу. Именно это и делают Mapped Types в TypeScript - они берут существующий тип и преобразуют его в новый тип на основе определенного набора правил.
Встроенные Mapped Types
TypeScript предоставляет некоторые预先 определенные Mapped Types, которые都非常 полезны. Давайте рассмотрим их по одному:
1. Partial
Тип Partial<T>
делает все свойства T
可选. Это как转变 строгого рецепта в гибкий, где вы можете пропустить некоторые ингредиенты.
interface Recipe {
name: string;
ingredients: string[];
cookingTime: number;
}
type FlexibleRecipe = Partial<Recipe>;
// Теперь мы можем создать рецепт без всех свойств
const quickSnack: FlexibleRecipe = {
name: "Toast",
// Мы можем пропустить ingredients и cookingTime
};
В этом примере FlexibleRecipe
позволяет нам создать рецепт без указания всех свойств. Это идеально, когда вы хотите обновить только часть объекта.
2. Required
Required<T>
- это相反 Partial<T>
. Он делает все свойства обязательными, даже если они были可选 в исходном типе.
interface UserProfile {
name: string;
age?: number;
email?: string;
}
type CompleteUserProfile = Required<UserProfile>;
// Теперь мы должны указать все свойства
const user: CompleteUserProfile = {
name: "Alice",
age: 30,
email: "[email protected]"
};
Здесь CompleteUserProfile
обеспечивает указание всех свойств, включая age
и email
, которые были可选 в исходном UserProfile
.
3. Readonly
Readonly<T>
делает все свойства T
только для чтения. Это как поместить ваш тип в стеклянный футляр - вы можете смотреть, но не можете трогать!
interface Toy {
name: string;
price: number;
}
type CollectibleToy = Readonly<Toy>;
const actionFigure: CollectibleToy = {
name: "Superhero",
price: 19.99
};
// Это вызовет ошибку
// actionFigure.price = 29.99;
В этом примере,.once мы создали наш actionFigure
, мы не можем изменять его свойства. Это великолепно для создания неизменяемых объектов.
4. Pick<T, K>
Pick<T, K>
создает новый тип, выбирая только указанные свойства K
из T
. Это как cherry-picking ваши любимые функции из типа.
interface Smartphone {
brand: string;
model: string;
year: number;
color: string;
price: number;
}
type BasicPhoneInfo = Pick<Smartphone, 'brand' | 'model'>;
const myPhone: BasicPhoneInfo = {
brand: "TechBrand",
model: "X2000"
};
Здесь BasicPhoneInfo
включает только свойства brand
и model
из Smartphone
, оставляя其余ые.
5. Omit<T, K>
Omit<T, K>
- это相反 Pick<T, K>
. Он создает новый тип, удаляя указанные свойства K
из T
.
interface Book {
title: string;
author: string;
pages: number;
isbn: string;
}
type BookPreview = Omit<Book, 'pages' | 'isbn'>;
const preview: BookPreview = {
title: "TypeScript Adventures",
author: "Code Wizard"
};
В этом случае BookPreview
включает все свойства Book
, кроме pages
и isbn
.
Примеры использования Mapped Types
Теперь, когда мы рассмотрели встроенные Mapped Types, давайте посмотрим на некоторые практические примеры их использования в реальных сценариях.
Пример 1: Создание состояния формы
Представьте, что вы создаете форму, и хотите отслеживать, какие поля были изменены:
interface LoginForm {
username: string;
password: string;
rememberMe: boolean;
}
type FormTouched = { [K in keyof LoginForm]: boolean };
const touchedFields: FormTouched = {
username: true,
password: false,
rememberMe: true
};
Здесь мы создали тип FormTouched
, который имеет те же ключи, что и LoginForm
, но все значения являются булевыми, указывающими, было ли поле touched.
Пример 2: Обертка для ответа API
Давайте представим, что у вас есть API, который возвращает различные типы данных, и вы хотите обернуть каждый ответ в стандартный формат:
interface ApiResponse<T> {
data: T;
status: 'success' | 'error';
timestamp: number;
}
type UserData = { id: number; name: string; email: string };
type ProductData = { id: number; name: string; price: number };
const userResponse: ApiResponse<UserData> = {
data: { id: 1, name: "John Doe", email: "[email protected]" },
status: 'success',
timestamp: Date.now()
};
const productResponse: ApiResponse<ProductData> = {
data: { id: 101, name: "Laptop", price: 999.99 },
status: 'success',
timestamp: Date.now()
};
Этот пример показывает, как мы можем использовать обобщения с Mapped Types для создания гибких, повторно используемых структур типов.
Создание пользовательских Mapped Types
Теперь давайте разомнем наши мышцы TypeScript и создадим некоторые пользовательские Mapped Types!
Пользовательский тип 1: Nullable
Давайте создадим тип, который делает все свойства nullable:
type Nullable<T> = { [K in keyof T]: T[K] | null };
interface Person {
name: string;
age: number;
}
type NullablePerson = Nullable<Person>;
const maybePerson: NullablePerson = {
name: "Jane",
age: null // Это теперь допустимо
};
Наш тип Nullable<T>
позволяет любому свойству быть либо его исходным типом, либо null
.
Пользовательский тип 2: Freezable
Давайте создадим тип, который добавляет метод freeze
к объекту:
type Freezable<T> = T & { freeze(): Readonly<T> };
interface Config {
theme: string;
fontSize: number;
}
function makeFreezable<T>(obj: T): Freezable<T> {
return {
...obj,
freeze() {
return Object.freeze({ ...this }) as Readonly<T>;
}
};
}
const config = makeFreezable<Config>({
theme: "dark",
fontSize: 14
});
const frozenConfig = config.freeze();
// frozenConfig.theme = "light"; // Это вызовет ошибку
Этот пользовательский тип добавляет метод freeze
к любому объекту, позволяя нам создать неизменяемую версию его.
Заключение
Ух, мы covered много ground сегодня! От встроенных Mapped Types до создания своих собственных пользовательских типов, вы видели, насколько мощным и гибким может быть TypeScript. Mapped Types - это как магические палочки в вашем наборе инструментов TypeScript - они позволяют вам преобразовывать и манипулировать типами самыми полезными способами.
Помните, ключ к maîtriser Mapped Types - это практика. Попробуйте создавать свои собственные, экспериментируйте с различными комбинациями, и вскоре вы будете писать код на TypeScript, который не только функционален, но и элегантен и типобезопасен.
Продолжайте программировать, продолжайте учиться и, самое главное, получайте удовольствие от работы с TypeScript!
Credits: Image by storyset