Сьогодні ми розберемо прикольні речі, які ви вже частково зустрічали в попередніх уроках, але в більш детальному контексті.
Маппінг типів
По факту це шаблон для створення нового типу, перебираючи властивості іншого типу.
type MyMappedType<T> = {
[K in keyof T]: T[K];
};
Цей шаблон означає: «для кожного ключа K з типу T, взяти тип T[K]». Як це можна використати, ну наприклад - зробити всі поля обовʼязковими:
type User = {
name?: string;
age?: number;
};
type RequiredUser = {
[K in keyof User]-?: User[K];
};
Створення типів на основі існуючих
TypeScript вже має вбудовані утилітарні типи, які базуються на маппінгу (ми вже їх робирали в попередніх уроках):
- Partial<T> - Робить усі властивості T необовʼязковими
- Required<T> - Робить усі властивості T обовʼязковими
- Readonly<T> - Робить усі властивості T readonly
- Pick<T, K> - Обирає підмножину властивостей
- Omit<T, K> - Виключає певні властивості
- Record<K, T> - Створює тип з ключами K і значеннями типу T
type Roles = 'admin' | 'user' | 'guest';
type RoleAccess = Record<Roles, boolean>;
// RoleAccess = { admin: boolean, user: boolean, guest: boolean }
Глибинна типізація обʼєктів
Іноді нам потрібно рекурсивно типізувати або модифікувати вкладені обʼєкти. Це вже вимагає рекурсивного маппінгу.
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
На прикладі реального коду:
type Config = {
db: {
host: string;
port: number;
};
logging: boolean;
};
const config: DeepReadonly<Config> = {
db: { host: 'localhost', port: 3306 },
logging: true,
};
// config.db.port = 5432 Помилка — властивість readonly
Ще один приклад, якщо вам потрібно змінити всі типи на string (глибоко)
type DeepStringify<T> = {
[K in keyof T]: T[K] extends object
? DeepStringify<T[K]>
: string;
};
// ---
type Data = {
id: number;
info: {
age: number;
active: boolean;
};
};
type Stringified = DeepStringify<Data>;
/*
{
id: string;
info: {
age: string;
active: string;
};
}
*/
Типізація глибокого доступу
Іноді хочемо мати тип, що представляє всі “шляхи” до полів:
type Path<T, Prev extends string = ''> = {
[K in keyof T]: T[K] extends object
? `${Prev}${K & string}` | Path<T[K], `${Prev}${K & string}.`>
: `${Prev}${K & string}`;
}[keyof T];
// --
type User = {
name: string;
address: {
city: string;
zip: number;
};
};
type UserPaths = Path<User>; // "name" | "address.city" | "address.zip"