Как исключить свойство из типа в TypeScript (Exclude property from type)
Использование встроенного типа Omit
Да, Omit<T, K> — это стандартное и наиболее распространенное решение для исключения свойств из типа. Он встроен в TypeScript начиная с версии 3.5. Ваш пример будет работать именно так, как вы ожидаете:
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
role: 'admin' | 'user';
}
// Исключаем password и createdAt
type PublicUser = Omit<User, 'password' | 'createdAt'>;
// Теперь PublicUser эквивалентен:
// {
// id: number;
// name: string;
// email: string;
// role: 'admin' | 'user';
// }
Как это работает под капотом
Omit — это комбинация двух других утилитарных типов: Pick и Exclude. Его определение выглядит так:
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
keyof T— получает объединение всех ключей типа T (например,'id' | 'name' | 'email' | 'password' | 'createdAt' | 'role').Exclude<keyof T, K>— исключает из этого объединения ключи, переданные в K (получаем'id' | 'name' | 'email' | 'role').Pick<T, ...>— выбирает из T только те свойства, ключи которых остались после исключения.
Практический пример с функцией
Вот как это можно использовать в реальном коде для безопасного возврата данных из API:
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
role: 'admin' | 'user';
}
// Тип для публичных данных пользователя
type PublicUser = Omit<User, 'password' | 'createdAt'>;
// Функция, которая "чистит" пользователя перед отправкой
function toPublicUser(user: User): PublicUser {
// Деструктуризация с исключением ненужных полей
const { password, createdAt, ...publicData } = user;
return publicData; // TypeScript проверит, что publicData соответствует PublicUser
}
// Использование
const user: User = {
id: 1,
name: 'Alice',
email: 'alice@example.com',
password: 'secret123',
createdAt: new Date(),
role: 'user'
};
const publicUser = toPublicUser(user);
console.log(publicUser); // { id: 1, name: 'Alice', email: 'alice@example.com', role: 'user' }
// password и createdAt отсутствуют
Альтернативы и расширенные случаи
Если вам нужно исключить свойства по их типу (а не по имени), можно написать свой утилитарный тип:
// Исключаем все свойства, которые являются функциями
type ExcludeFunctionProperties<T> = {
[K in keyof T as T[K] extends Function ? never : K]: T[K]
};
interface Service {
name: string;
start(): void;
stop(): void;
version: number;
}
type ServiceWithoutMethods = ExcludeFunctionProperties<Service>;
// { name: string; version: number; }
Но для 95% случаев Omit — это именно то, что вам нужно. Он лаконичен, понятен и полностью типобезопасен. Вы также можете комбинировать его с другими типами:
// Исключаем свойства и делаем остальные readonly
type ReadonlyPublicUser = Readonly<Omit<User, 'password' | 'createdAt'>>;
Важные замечания
Omitработает только с плоскими объектами. Для вложенных объектов нужно применять его рекурсивно или использовать библиотеки вродеtype-fest.- Если вы попытаетесь исключить свойство, которого нет в типе, TypeScript выдаст ошибку — это защита от опечаток.
- Для интерфейсов и типов
Omitработает одинаково хорошо.
Используйте Omit везде, где нужно создать "подтип" существующего типа, исключив некоторые поля. Это гораздо лучше, чем дублировать интерфейсы вручную!
Создание собственного утилитарного типа ExcludeProperty
Если вам не нравится название Omit (оно может быть неочевидным для новичков) или вы хотите добавить дополнительную логику, можно легко создать свой тип:
// Собственная реализация с понятным названием
type ExcludeProperty<T, K extends keyof T> = Omit<T, K>;
// Или с дополнительной валидацией
type ExcludePropertyStrict<T, K extends keyof T> = {
[P in Exclude<keyof T, K>]: T[P]
};
// Использование
interface User {
id: number;
name: string;
email: string;
password: string;
}
type PublicUser = ExcludeProperty<User, 'password'>;
// { id: number; name: string; email: string; }
Исключение свойств по условию
Иногда нужно исключать не по имени, а по значению. Например, убрать все поля, которые могут быть null или undefined:
type ExcludeNullable<T> = {
[K in keyof T as null extends T[K] ? never : K]: T[K]
};
interface Config {
url: string;
timeout?: number; // может быть undefined
retries: number | null; // может быть null
}
type StrictConfig = ExcludeNullable<Config>;
// { url: string; } — timeout и retries исключены
Такой подход полезен, когда вы работаете с данными из ненадежных источников и хотите гарантировать, что в итоговом типе останутся только обязательные не-null поля. Однако для простого исключения конкретных свойств Omit остается лучшим выбором.