38. Mapped Type Modifiers
TypeScript: Броня. Урок 37: Модификаторы Mapped Types
Заголовок раздела «TypeScript: Броня. Урок 37: Модификаторы Mapped Types»Модификаторы в mapped types (readonly, ?, +, -) позволяют добавлять или удалять свойства типов. Понимание модификаторов критически важно для создания мощных трансформаций типов и utility types.
Readonly Modifier
Заголовок раздела «Readonly Modifier»// Добавление readonlytype Readonly<T> = { readonly [P in keyof T]: T[P];};
interface User { id: string; name: string; age: number;}
type ReadonlyUser = Readonly<User>;// {// readonly id: string;// readonly name: string;// readonly age: number;// }
// Удаление readonly с помощью '-readonly'type Mutable<T> = { -readonly [P in keyof T]: T[P];};
interface ImmutableUser { readonly id: string; readonly name: string; readonly age: number;}
type MutableUser = Mutable<ImmutableUser>;// {// id: string;// name: string;// age: number;// }Optional Modifier (?)
Заголовок раздела «Optional Modifier (?)»// Добавление optional (?)type Partial<T> = { [P in keyof T]?: T[P];};
type PartialUser = Partial<User>;// {// id?: string;// name?: string;// age?: number;// }
// Удаление optional с помощью '-?'type Required<T> = { [P in keyof T]-?: T[P];};
interface OptionalUser { id?: string; name?: string; age?: number;}
type RequiredUser = Required<OptionalUser>;// {// id: string;// name: string;// age: number;// }Explicit Modifiers (+)
Заголовок раздела «Explicit Modifiers (+)»// '+' делает добавление модификатора явным (по умолчанию)type ExplicitReadonly<T> = { +readonly [P in keyof T]: T[P];};
type ExplicitPartial<T> = { [P in keyof T]+?: T[P];};
// '+' обычно не нужен, но может улучшить читаемостьtype Readonly2<T> = { +readonly [P in keyof T]: T[P];};
type Partial2<T> = { [P in keyof T]+?: T[P];};Комбинирование Модификаторов
Заголовок раздела «Комбинирование Модификаторов»// Readonly + Optionaltype ReadonlyPartial<T> = { +readonly [P in keyof T]+?: T[P];};
type ReadonlyPartialUser = ReadonlyPartial<User>;// {// readonly id?: string;// readonly name?: string;// readonly age?: number;// }
// Mutable + Requiredtype MutableRequired<T> = { -readonly [P in keyof T]-?: T[P];};
interface WeirdUser { readonly id?: string; readonly name?: string; readonly age?: number;}
type NormalUser = MutableRequired<WeirdUser>;// {// id: string;// name: string;// age: number;// }Практический пример: State Management
Заголовок раздела «Практический пример: State Management»// Immutable state с модификаторамиtype ImmutableState<T> = { +readonly [P in keyof T]: T[P] extends object ? ImmutableState<T[P]> : T[P];};
interface AppState { user: { id: string; profile: { name: string; email: string; }; }; settings: { theme: 'light' | 'dark'; language: string; };}
type ImmutableAppState = ImmutableState<AppState>;// Все поля рекурсивно readonly
const state: ImmutableAppState = { user: { id: '123', profile: { name: 'Alice', }, }, settings: { theme: 'dark', language: 'en', },};
// state.user.id = '456'; // ✗ readonly// state.user.profile.name = 'Bob'; // ✗ readonlyConditional Modifiers
Заголовок раздела «Conditional Modifiers»// Условное применение модификаторовtype ReadonlyIf<T, Condition extends boolean> = Condition extends true ? { +readonly [P in keyof T]: T[P] } : T;
type User1 = ReadonlyIf<User, true>; // Readonly<User>type User2 = ReadonlyIf<User, false>; // User
// Partial для определённых типовtype PartialByType<T, ValueType> = { [P in keyof T as T[P] extends ValueType ? P : never]?: T[P];} & { [P in keyof T as T[P] extends ValueType ? never : P]: T[P];};
interface Product { id: string; name: string; price: number; stock: number;}
type PartialNumbers = PartialByType<Product, number>;// {// id: string;// name: string;// price?: number;// stock?: number;// }Selective Modifiers
Заголовок раздела «Selective Modifiers»// Применение модификаторов к выбранным полямtype ReadonlyBy<T, K extends keyof T> = Omit<T, K> & { +readonly [P in K]: T[P];};
type UserWithReadonlyId = ReadonlyBy<User, 'id'>;// {// name: string;// age: number;// readonly id: string;// }
type PartialBy<T, K extends keyof T> = Omit<T, K> & { [P in K]+?: T[P];};
type UserWithOptionalAge = PartialBy<User, 'age'>;// {// id: string;// name: string;// age?: number;// }
type RequiredBy<T, K extends keyof T> = T & { [P in K]-?: T[P];};
interface Config { host?: string; port?: number; ssl?: boolean;}
type ConfigWithRequiredHost = RequiredBy<Config, 'host'>;// {// host: string; // required// port?: number;// ssl?: boolean;// }Жизненный пример: Form Fields
Заголовок раздела «Жизненный пример: Form Fields»// Система управления формами с модификаторамиinterface FormField<T> { value: T; defaultValue: T; error: string | null; touched: boolean; required: boolean;}
// Все поля readonly после отправкиtype SubmittedForm<T extends Record<string, any>> = { +readonly [K in keyof T]: FormField<T[K]>;};
// Поля optional для initial statetype InitialForm<T extends Record<string, any>> = { [K in keyof T]+?: Partial<FormField<T[K]>>;};
// Validated form - все requiredtype ValidatedForm<T extends Record<string, any>> = { [K in keyof T]-?: FormField<T[K]>;};
// Определение формыinterface LoginFormData { email: string; password: string;}
type LoginFormInitial = InitialForm<LoginFormData>;// {// email?: Partial<FormField<string>>;// password?: Partial<FormField<string>>;// }
type LoginFormValidated = ValidatedForm<LoginFormData>;// {// email: FormField<string>;// password: FormField<string>;// }
type LoginFormSubmitted = SubmittedForm<LoginFormData>;// {// readonly email: FormField<string>;// readonly password: FormField<string>;// }Nested Modifiers
Заголовок раздела «Nested Modifiers»// Deep модификаторыtype DeepReadonly<T> = T extends object ? { +readonly [P in keyof T]: DeepReadonly<T[P]> } : T;
type DeepPartial<T> = T extends object ? { [P in keyof T]+?: DeepPartial<T[P]> } : T;
type DeepRequired<T> = T extends object ? { [P in keyof T]-?: DeepRequired<T[P]> } : T;
type DeepMutable<T> = T extends object ? { -readonly [P in keyof T]: DeepMutable<T[P]> } : T;
// Использованиеinterface NestedData { user: { profile: { name: string; age: number; }; settings: { theme: string; notifications: boolean; }; };}
type DeepReadonlyData = DeepReadonly<NestedData>;// Все вложенные поля readonly
type DeepPartialData = DeepPartial<NestedData>;// Все вложенные поля optionalModifier Helpers
Заголовок раздела «Modifier Helpers»// Проверка наличия readonlytype IsReadonly<T, K extends keyof T> = { readonly [P in K]: T[P];} extends { [P in K]: T[P] } ? false : true;
interface TestReadonly { readonly id: string; name: string;}
type IdIsReadonly = IsReadonly<TestReadonly, 'id'>; // truetype NameIsReadonly = IsReadonly<TestReadonly, 'name'>; // false
// Проверка наличия optionaltype IsOptional<T, K extends keyof T> = {} extends Pick<T, K> ? true : false;
interface TestOptional { id: string; name?: string;}
type IdIsOptional = IsOptional<TestOptional, 'id'>; // falsetype NameIsOptional = IsOptional<TestOptional, 'name'>; // true
// Получение readonly ключейtype ReadonlyKeys<T> = { [K in keyof T]-?: IsReadonly<T, K> extends true ? K : never;}[keyof T];
type ReadonlyTestKeys = ReadonlyKeys<TestReadonly>; // "id"
// Получение optional ключейtype OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never;}[keyof T];
type OptionalTestKeys = OptionalKeys<TestOptional>; // "name"Advanced Patterns
Заголовок раздела «Advanced Patterns»// Conditional модификаторы на основе типа значенияtype ReadonlyIfObject<T> = { [P in keyof T as T[P] extends object ? P : never]+readonly: T[P];} & { [P in keyof T as T[P] extends object ? never : P]: T[P];};
interface Mixed { id: string; config: { setting: string; }; count: number;}
type SelectiveReadonly = ReadonlyIfObject<Mixed>;// {// id: string;// count: number;// readonly config: { setting: string };// }
// Toggle модификаторовtype ToggleReadonly<T> = { [P in keyof T as IsReadonly<T, P> extends true ? P : never]-readonly: T[P];} & { [P in keyof T as IsReadonly<T, P> extends false ? P : never]+readonly: T[P];};
interface ToToggle { readonly id: string; name: string;}
type Toggled = ToggleReadonly<ToToggle>;// {// id: string; // было readonly, стало mutable// readonly name: string; // было mutable, стало readonly// }Ключевые моменты
Заголовок раздела «Ключевые моменты»readonlyделает свойства неизменяемыми?делает свойства optional+явно добавляет модификатор (по умолчанию)-удаляет модификатор- Модификаторы можно комбинировать
-readonlyи-?удаляют модификаторы- Deep версии рекурсивно применяют модификаторы
- Можно применять модификаторы селективно к определённым полям
- Conditional модификаторы зависят от условий
- Можно проверять наличие модификаторов с helper типами
- Используются в state management, forms, API DTOs
- Критически важны для immutability и type safety