- Мнемоническая таблица по категориям
- Использование typeof с утилитными типами
- Комбинирование утилитных типов с примерами
- Создание собственных утилитных типов - подробное объяснение
| Утилитный тип | Мнемоника | Что делает |
|---|---|---|
Partial<T> |
"Частичный" | Делает все свойства необязательными |
Required<T> |
"Обязательный" | Делает все свойства обязательными |
Pick<T, K> |
"Выбрать" | Выбирает указанные свойства |
Omit<T, K> |
"Опустить" | Исключает указанные свойства |
Record<K, T> |
"Запись в БД" | Создает объект с ключами K и значениями T |
Readonly<T> |
"Только чтение" | Делает все свойства только для чтения |
| Утилитный тип | Мнемоника | Что делает |
|---|---|---|
ReturnType<T> |
"Тип выхода" | Извлекает тип возвращаемого значения |
Parameters<T> |
"Тип входа" | Извлекает типы параметров функции |
OmitThisParameter<T> |
"Без контекста" | Удаляет this-параметр |
ThisParameterType<T> |
"Тип контекста" | Извлекает тип this-параметра |
| Утилитный тип | Мнемоника | Что делает |
|---|---|---|
InstanceType<T> |
"Что возвращает new" | Тип экземпляра класса |
ConstructorParameters<T> |
"Что принимает new" | Типы параметров конструктора |
| Утилитный тип | Мнемоника | Что делает |
|---|---|---|
NonNullable<T> |
"Не нулевой" | Удаляет null и undefined из типа |
Exclude<T, U> |
"Исключить" | Исключает типы из T, присутствующие в U |
Extract<T, U> |
"Извлечь" | Извлекает из T типы, присутствующие в U |
| Утилитный тип | Мнемоника | Что делает |
|---|---|---|
ThisType<T> |
"Маркер контекста" | Маркирует тип this в объектах |
Многие утилитные типы TypeScript обычно используются в сочетании с оператором typeof. Это не просто стилистический выбор, а необходимость, которая обусловлена типовой системой TypeScript.
// Для функций почти всегда нужен typeof
function createUser() {
return { id: 1, name: "John" };
}
// Правильно - с typeof
type User = ReturnType<typeof createUser>;
// { id: number; name: string; }
// Неправильно - без typeof
// type User = ReturnType<createUser>; // Ошибка: 'createUser' refers to a value, but is being used as a type hereПри использовании Parameters результат возвращается в виде кортежа (tuple):
function fetchData(url: string, options: { method: string; headers?: Record<string, string> }) {
// реализация
}
type FetchParams = Parameters<typeof fetchData>;
// [url: string, options: { method: string; headers?: Record<string, string> }]
// Можно обращаться к параметрам по индексу
type OptionsType = FetchParams[1]; // { method: string; headers?: Record<string, string> }С классами ситуация аналогична - почти всегда требуется использовать typeof:
class User {
constructor(public id: number, public name: string) {}
greet() { return `Hello, ${this.name}`; }
}
// Правильно - с typeof
type UserInstance = InstanceType<typeof User>;
// { id: number; name: string; greet(): string; }
// Неправильно - ошибка компиляции
// type UserInstance = InstanceType<User>; // 'User' refers to a value, but is being used as a type
// То же самое с ConstructorParameters
type UserConstructorParams = ConstructorParameters<typeof User>;
// [id: number, name: string]Важно понимать разницу между InstanceType и ConstructorParameters:
InstanceType<typeof Class>- тип экземпляра класса (тип объекта, создаваемого черезnew Class())ConstructorParameters<typeof Class>- типы параметров конструктора (параметры, передаваемые вnew Class(...))
Визуализация:
new User(1, "John") → User { id: 1, name: "John" }
↑ ↑
ConstructorParameters InstanceType
ThisParameterType извлекает тип this-параметра функции, когда он явно указан:
// Функция с явным this-параметром
function greet(this: { name: string }, prefix: string) {
return `${prefix}, ${this.name}!`;
}
// Получаем тип this-параметра
type GreetThisType = ThisParameterType<typeof greet>; // { name: string }
// Использование
const user = { name: "Alice", age: 30 };
greet.call(user, "Привет"); // OK
const invalid = { age: 25 };
// greet.call(invalid, "Привет"); // Ошибка: объект не имеет свойства 'name'Необходимость использования typeof с утилитными типами вроде ReturnType, Parameters, InstanceType связана с фундаментальным различием между значениями и типами в TypeScript:
-
Две отдельные сферы существования:
- Значения (функции, переменные, классы как конструкторы) - существуют во время выполнения
- Типы (интерфейсы, типы) - существуют только во время компиляции
-
Преобразование между мирами:
typeofв контексте типов - преобразует значение в его типtype User = typeof user- получает тип значенияuser
-
Почему это важно:
// Функция как значение
function process() { return 42; }
// Интерфейс как тип
interface Config { debug: boolean; }
// Утилитные типы работают с ТИПАМИ, а не ЗНАЧЕНИЯМИ
type ProcessReturnType = ReturnType<typeof process>; // OK: number
// type InvalidType = ReturnType<process>; // Ошибка: process - это значение, а не тип
type ConfigKeys = keyof Config; // OK: "debug"
// type InvalidKeys = keyof process; // Ошибка: process - это значение, а не типЭта концепция разделения значений и типов - одна из ключевых особенностей TypeScript. Она позволяет системе типов быть выразительной, не влияя на выполнение программы.
Запомните правило: если вы работаете с функцией, классом или другим значением в контексте типов, почти всегда нужно использовать typeof.
// Начальный тип
interface User {
id: number;
name: string;
email: string;
password: string;
lastLogin: Date;
}
// Комбинирование: Pick + Readonly
type ReadonlyUserBasicInfo = Readonly<Pick<User, 'id' | 'name'>>;
// Эквивалентно:
// {
// readonly id: number;
// readonly name: string;
// }
const userInfo: ReadonlyUserBasicInfo = { id: 1, name: "Иван" };
// userInfo.name = "Петр"; // Ошибка: Cannot assign to 'name' because it is a read-only property// Функция, которая может вернуть null
function fetchData(): { data: string[] } | null {
// ...
return Math.random() > 0.5 ? { data: ["abc"] } : null;
}
// Комбинирование: ReturnType + NonNullable
type SafeApiResult = NonNullable<ReturnType<typeof fetchData>>;
// Эквивалентно:
// {
// data: string[];
// }
// Использование:
function processSafeData(data: SafeApiResult) {
// Здесь не нужна проверка на null
console.log(data.data.length);
}// Класс с конструктором
class UserManager {
constructor(
userId: number,
options: {
includeDetails: boolean;
fetchRoles: boolean;
cacheTTL: number;
}
) {
// ...
}
}
// Комбинирование: ConstructorParameters + Partial
type UserManagerParams = ConstructorParameters<typeof UserManager>;
type OptionalUserManagerOptions = Partial<UserManagerParams[1]>;
// Эквивалентно:
// {
// includeDetails?: boolean;
// fetchRoles?: boolean;
// cacheTTL?: number;
// }
// Функция с опциональными параметрами
function createUserManager(userId: number, options?: OptionalUserManagerOptions) {
const defaultOptions = {
includeDetails: false,
fetchRoles: false,
cacheTTL: 3600
};
return new UserManager(
userId,
{ ...defaultOptions, ...options }
);
}Стандартный Readonly<T> делает только "поверхностную" заморозку объекта - вложенные объекты остаются изменяемыми:
interface User {
id: number;
name: string;
settings: {
theme: string;
notifications: boolean;
};
}
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = {
id: 1,
name: "Alice",
settings: { theme: "dark", notifications: true }
};
// Ошибка - нельзя изменить свойство верхнего уровня
// user.name = "Bob";
// Но вложенный объект можно изменить!
user.settings.theme = "light"; // Работает без ошибокСоздадим рекурсивный тип DeepReadonly, который замораживает все уровни вложенности:
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? T[K] extends Function
? T[K]
: DeepReadonly<T[K]>
: T[K];
};
// Использование
type DeepReadonlyUser = DeepReadonly<User>;
const deepUser: DeepReadonlyUser = {
id: 1,
name: "Alice",
settings: { theme: "dark", notifications: true }
};
// Ошибка - нельзя изменить свойство верхнего уровня
// deepUser.name = "Bob";
// Теперь и вложенный объект нельзя изменить!
// deepUser.settings.theme = "light"; // Ошибка: Cannot assign to 'theme' because it is a read-only propertyРазбор типа DeepReadonly:
[K in keyof T]- перебираем все ключи типа Treadonly- делаем каждое свойство только для чтенияT[K] extends object ? ... : T[K]- проверяем, является ли значение объектомT[K] extends Function ? T[K] : DeepReadonly<T[K]>- если это функция, оставляем как есть, иначе рекурсивно применяем DeepReadonly- Рекурсия позволяет обрабатывать вложенные объекты любой глубины
Создадим тип, противоположный NonNullable - делающий тип допускающим null и undefined:
type Nullable<T> = T | null | undefined;
// Использование
function fetchUser(id: number): Nullable<User> {
// Может вернуть пользователя, null или undefined
if (id < 0) return null;
if (id === 0) return undefined;
return { id, name: "User " + id };
}
// Теперь нужно проверять результат перед использованием
const user = fetchUser(5);
if (user) {
console.log(user.name); // Безопасно, только если user не null/undefined
}Разбор типа Nullable:
T | null | undefined- объединяет исходный тип с null и undefined- Это простое объединение типов (union type)
- Заставляет TypeScript требовать проверки на null/undefined перед использованием
Стандартный Partial<T> делает необязательными только свойства верхнего уровня. Создадим рекурсивную версию:
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object
? T[K] extends Array<infer U>
? Array<DeepPartial<U>>
: T[K] extends Function
? T[K]
: DeepPartial<T[K]>
: T[K];
};
// Исходный сложный тип
interface Config {
server: {
host: string;
port: number;
ssl: {
enabled: boolean;
cert: string;
key: string;
};
};
database: {
url: string;
credentials: {
username: string;
password: string;
};
};
features: string[];
}
// Использование DeepPartial для частичного обновления
function updateConfig(config: Config, updates: DeepPartial<Config>): Config {
// Реализация слияния объектов
return deepMerge(config, updates);
}
// Теперь можно передать только нужные поля на любом уровне вложенности
updateConfig(currentConfig, {
server: {
ssl: {
enabled: true
// Не нужно указывать cert и key
}
}
// Не нужно указывать database и features
});Разбор типа DeepPartial:
[K in keyof T]?- перебираем все ключи T и делаем их необязательными (?)- Проверяем, является ли свойство объектом:
T[K] extends object - Специальная обработка для массивов:
T[K] extends Array<infer U>с использованиемinferдля извлечения типа элементов - Специальная обработка для функций:
T[K] extends Function ? T[K] - Рекурсивное применение
DeepPartialдля вложенных объектов