Що таке "mapped type"?
Що таке Mapped Type
Mapped type - це тип, що створюється на основі іншого типу (або множини ключів) шляхом ітерації по його ключах і модифікації властивостей (їхніх типів, імен, модифікаторів).
Можна сказати, що це "типовий аналог циклу for...in по ключах об'єкта".
Синтаксис
type NewType = {
[K in Keys]: Type;
}K- змінна, що представляє ключ;in- оператор, який перебирає значення об'єднанняKeys;Type- тип значення, який ми призначаємо кожному ключу.
Приклад 1. Простий mapping
type User = {
name: string;
age: number;
};
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
// Перетворений тип:
type NullableUser = Nullable<User>;
/*
{
name: string | null;
age: number | null;
}
*/Тут:
keyof T- це"name" | "age";[K in keyof T]- перебирає"name"і"age";- кожному полю присвоюється
T[K] | null.
Приклад 2. Використання модифікаторів
У mapped types можна змінювати модифікатори властивостей:
| Модифікатор | Що робить |
|---|---|
? | робить властивість опціональною |
-? | робить властивість обов'язковою |
readonly | робить властивість тільки для читання |
-readonly | прибирає readonly |
Приклад
type Optional<T> = {
[K in keyof T]?: T[K];
};
type ReadonlyCopy<T> = {
readonly [K in keyof T]: T[K];
};
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
type RequiredCopy<T> = {
[K in keyof T]-?: T[K];
};Приклад 3. Реалізація стандартних утиліт
Ось як влаштовані багато вбудованих utility types:
// Partial<T>
type Partial<T> = {
[K in keyof T]?: T[K];
};
// Required<T>
type Required<T> = {
[K in keyof T]-?: T[K];
};
// Readonly<T>
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
// Pick<T, K>
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
// Record<K, T>
type Record<K extends string | number | symbol, T> = {
[P in K]: T;
};Приклад 4. Перетворення ключів
Можна змінювати імена властивостей за допомогою конструкції as (TypeScript >= 4.1):
type UppercaseKeys<T> = {
[K in keyof T as Uppercase<string & K>]: T[K];
};
type User = {
name: string;
age: number;
};
type UpperUser = UppercaseKeys<User>;
/*
{
NAME: string;
AGE: number;
}
*/Тут
as Uppercase<...>перейменовує ключі у верхній регістр.
Приклад 5. Фільтрація ключів
Можна навіть видаляти ключі з типу за допомогою never:
type RemoveNever<T> = {
[K in keyof T as T[K] extends never ? never : K]: T[K];
};
type Example = {
a: string;
b: never;
c: number;
};
type Clean = RemoveNever<Example>;
// { a: string; c: number }Приклад 6. Комбінування з conditional types
Mapped types часто поєднуються з conditional types для гнучких перетворень:
type MakeOptionalIfNullable<T> = {
[K in keyof T as null extends T[K] ? K : never]?: T[K];
};
type User = {
id: number;
name: string | null;
age: number;
};
type OptionalNullable = MakeOptionalIfNullable<User>;
// { name?: string | null }Приклад 7. Динамічне створення API типів
type Events = "click" | "submit" | "hover";
type EventHandlers = {
[E in Events as `on${Capitalize<E>}`]: (event: E) => void;
};
/*
{
onClick: (event: "click") => void;
onSubmit: (event: "submit") => void;
onHover: (event: "hover") => void;
}
*/Як думає TypeScript
TypeScript ніби робить таке:
// псевдокод
let result = {};
for (let key of Keys) {
result[key] = Type;
}
return result;Переваги mapped types
Дозволяють автоматично створювати похідні типи
Виключають дублювання коду (не потрібно вручну переписувати поля)
Чудово працюють з keyof, Conditional Types, Template Literals
Основа всіх вбудованих утилітарних типів
Підсумок
| Поняття | Опис |
|---|---|
| Mapped type | Тип, що створюється шляхом ітерації по ключах іншого типу |
| Синтаксис | [K in Keys]: Type |
| Додаткові можливості | readonly, ?, as, never |
| Заснований на | keyof, in, conditional types |
| Приклади в TS | Partial, Required, Readonly, Pick, Record |
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.