Skip to main content

Що таке "mapped type"?

Що таке Mapped Type

Mapped type - це тип, що створюється на основі іншого типу (або множини ключів) шляхом ітерації по його ключах і модифікації властивостей (їхніх типів, імен, модифікаторів).

Можна сказати, що це "типовий аналог циклу for...in по ключах об'єкта".


Синтаксис

javascript
type NewType = { [K in Keys]: Type; }
  • K - змінна, що представляє ключ;
  • in - оператор, який перебирає значення об'єднання Keys;
  • Type - тип значення, який ми призначаємо кожному ключу.

Приклад 1. Простий mapping

javascript
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

Приклад

javascript
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:

javascript
// 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):

javascript
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:

javascript
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 для гнучких перетворень:

javascript
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 типів

javascript
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 ніби робить таке:

javascript
// псевдокод 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
Приклади в TSPartial, Required, Readonly, Pick, Record

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.