37. Создание своих Utility Types
TypeScript: Броня. Урок 36: Создание Custom Utility Types
Заголовок раздела «TypeScript: Броня. Урок 36: Создание Custom Utility Types»Создание собственных utility types - это мощный навык, который позволяет создавать переиспользуемые типовые абстракции для вашего проекта. Знание того, как комбинировать встроенные utility types и создавать кастомные, критически важно для advanced TypeScript разработки.
Базовые Custom Utility Types
Заголовок раздела «Базовые Custom Utility Types»// Делаем только определённые поля optionaltype Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
interface User { id: string; name: string; email: string; age: number;}
// id и name обязательны, email и age опциональныtype UserRegistration = Optional<User, 'email' | 'age'>;// {// id: string;// name: string;// email?: string;// age?: number;// }
// Делаем только определённые поля readonlytype ReadonlyBy<T, K extends keyof T> = Omit<T, K> & Readonly<Pick<T, K>>;
// id readonly, остальные можно менятьtype UserWithReadonlyId = ReadonlyBy<User, 'id'>;
// Делаем только определённые поля requiredtype RequiredBy<T, K extends keyof T> = T & Required<Pick<T, K>>;
interface Config { host?: string; port?: number; ssl?: boolean;}
// host обязателен, остальные опциональныtype ConfigWithRequiredHost = RequiredBy<Config, 'host'>;// {// host: string;// port?: number;// ssl?: boolean;// }Nullable и Optional Types
Заголовок раздела «Nullable и Optional Types»// Делает все поля nullabletype Nullable<T> = { [K in keyof T]: T[K] | null;};
type NullableUser = Nullable<User>;// {// id: string | null;// name: string | null;// email: string | null;// age: number | null;// }
// Делает все поля optional и nullabletype Maybe<T> = { [K in keyof T]?: T[K] | null;};
type MaybeUser = Maybe<User>;// {// id?: string | null;// name?: string | null;// email?: string | null;// age?: number | null;// }
// Unwrap nullabletype NonNullableFields<T> = { [K in keyof T]: NonNullable<T[K]>;};Deep Utility Types
Заголовок раздела «Deep Utility Types»// Deep Partial - рекурсивно делает все поля optionaltype DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];};
// Deep Readonly - рекурсивно делает все поля readonlytype DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];};
// Deep Required - рекурсивно делает все поля requiredtype DeepRequired<T> = { [P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P];};
// Deep Nullable - рекурсивно добавляет nulltype DeepNullable<T> = { [P in keyof T]: T[P] extends object ? DeepNullable<T[P]> | null : T[P] | null;};
// Пример использованияinterface NestedConfig { server: { host: string; port: number; ssl: { enabled: boolean; cert: string; }; };}
type PartialNestedConfig = DeepPartial<NestedConfig>;// Все вложенные поля опциональныMutable Types
Заголовок раздела «Mutable Types»// Удаляет readonly модификаторыtype Mutable<T> = { -readonly [P in keyof T]: T[P];};
interface ReadonlyUser { readonly id: string; readonly name: string; readonly email: string;}
type MutableUser = Mutable<ReadonlyUser>;// {// id: string;// name: string;// email: string;// }
// Deep Mutabletype DeepMutable<T> = { -readonly [P in keyof T]: T[P] extends object ? DeepMutable<T[P]> : T[P];};Filtering Types
Заголовок раздела «Filtering Types»// Выбирает только поля определённого типаtype PickByType<T, ValueType> = { [K in keyof T as T[K] extends ValueType ? K : never]: T[K];};
interface Mixed { id: string; name: string; age: number; count: number; active: boolean;}
type OnlyStrings = PickByType<Mixed, string>;// { id: string; name: string }
type OnlyNumbers = PickByType<Mixed, number>;// { age: number; count: number }
// Исключает поля определённого типаtype OmitByType<T, ValueType> = { [K in keyof T as T[K] extends ValueType ? never : K]: T[K];};
type NoStrings = OmitByType<Mixed, string>;// { age: number; count: number; active: boolean }
// Только функцииtype FunctionKeys<T> = { [K in keyof T]: T[K] extends Function ? K : never;}[keyof T];
type PickFunctions<T> = Pick<T, FunctionKeys<T>>;
interface UserService { name: string; getUser(): User; deleteUser(id: string): void; updateUser(user: User): Promise<void>;}
type ServiceMethods = PickFunctions<UserService>;// {// getUser: () => User;// deleteUser: (id: string) => void;// updateUser: (user: User) => Promise<void>;// }Практический пример: API DTO Types
Заголовок раздела «Практический пример: API DTO Types»// Автоматическое создание DTO типовtype CreateDTO<T> = Omit<T, 'id' | 'createdAt' | 'updatedAt'>;
type UpdateDTO<T> = Partial<Omit<T, 'id' | 'createdAt' | 'updatedAt'>>;
type ResponseDTO<T> = Omit<T, never>; // Все поля as-is
interface BaseEntity { id: string; createdAt: Date; updatedAt: Date;}
interface Product extends BaseEntity { name: string; description: string; price: number; categoryId: string;}
// Автоматически сгенерированные DTOstype CreateProductDTO = CreateDTO<Product>;// {// name: string;// description: string;// price: number;// categoryId: string;// }
type UpdateProductDTO = UpdateDTO<Product>;// {// name?: string;// description?: string;// price?: number;// categoryId?: string;// }
// Более продвинутый DTO generatortype ApiDTO<T, Mode extends 'create' | 'update' | 'response'> = Mode extends 'create' ? CreateDTO<T> : Mode extends 'update' ? UpdateDTO<T> : ResponseDTO<T>;
type ProductCreate = ApiDTO<Product, 'create'>;type ProductUpdate = ApiDTO<Product, 'update'>;type ProductResponse = ApiDTO<Product, 'response'>;Жизненный пример: Form Builder Types
Заголовок раздела «Жизненный пример: Form Builder Types»// Type-safe форма buildertype FormValue = string | number | boolean | Date;
type FormField<T extends FormValue> = { value: T; defaultValue: T; required: boolean; error: string | null; touched: boolean;};
type FormConfig<T extends Record<string, FormValue>> = { [K in keyof T]: Omit<FormField<T[K]>, 'value' | 'error' | 'touched'>;};
type FormState<T extends Record<string, FormValue>> = { [K in keyof T]: FormField<T[K]>;};
// Validation rulestype ValidationRule<T> = (value: T) => string | null;
type FormValidation<T extends Record<string, FormValue>> = { [K in keyof T]?: ValidationRule<T[K]>[];};
// Extract form valuestype FormValues<T extends Record<string, FormValue>> = { [K in keyof T]: T[K];};
// Extract touched fieldstype TouchedFields<T extends Record<string, FormValue>> = { [K in keyof T]: boolean;};
// Extract errorstype FormErrors<T extends Record<string, FormValue>> = { [K in keyof T]: string | null;};
// Пример использованияinterface LoginForm { email: string; password: string; rememberMe: boolean;}
type LoginFormConfig = FormConfig<LoginForm>;type LoginFormState = FormState<LoginForm>;type LoginFormValidation = FormValidation<LoginForm>;type LoginFormValues = FormValues<LoginForm>;Rename Keys
Заголовок раздела «Rename Keys»// Переименование ключейtype RenameKey<T, OldKey extends keyof T, NewKey extends string> = Omit<T, OldKey> & Record<NewKey, T[OldKey]>;
interface User { id: string; name: string;}
type UserWithUserId = RenameKey<User, 'id', 'userId'>;// { name: string; userId: string }
// Rename multiple keystype RenameKeys<T, KeyMap extends Partial<Record<keyof T, string>>> = { [K in keyof T as K extends keyof KeyMap ? KeyMap[K] extends string ? KeyMap[K] : K : K]: T[K];};
type UserRenamed = RenameKeys<User, { id: 'userId'; name: 'userName' }>;// { userId: string; userName: string }Flatten Types
Заголовок раздела «Flatten Types»// Flatten nested objecttype Flatten<T> = T extends object ? { [K in keyof T]: T[K]; } : T;
type User = { id: string;} & { name: string;} & { email: string;};
type FlatUser = Flatten<User>;// { id: string; name: string; email: string }
// Expand - делает тип более читаемым в IDEtype Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
type Complex = Pick<User, 'id'> & Pick<User, 'name'>;type Expanded = Expand<Complex>;// { id: string; name: string }Merge Types
Заголовок раздела «Merge Types»// Merge с overridetype Merge<T, U> = Omit<T, keyof U> & U;
interface Base { id: string; name: string; age: number;}
interface Override { age: string; // меняем тип email: string; // добавляем новое поле}
type Merged = Merge<Base, Override>;// {// id: string;// name: string;// age: string; // переопределён// email: string; // добавлен// }
// Deep Mergetype DeepMerge<T, U> = { [K in keyof T | keyof U]: K extends keyof U ? U[K] : K extends keyof T ? T[K] extends object ? U[K & keyof U] extends object ? DeepMerge<T[K], U[K & keyof U]> : T[K] : T[K] : never;};Ключевые моменты
Заголовок раздела «Ключевые моменты»- Custom utility types создаются комбинированием встроенных utility types
Omit+Partial+Pick- основа для многих кастомных типов- Deep версии используют рекурсию для вложенных структур
- Filtering types используют conditional types и mapped types
- Можно создавать типы для автоматической генерации DTOs
- RenameKeys позволяет переименовывать свойства
- Merge types полезны для комбинирования типов с override
- Expand/Flatten улучшают читаемость сложных типов в IDE
- Создавайте библиотеку utility types для вашего проекта
- Документируйте сложные utility types для команды