Omit<T, K> это встроенный служебный тип, который создаёт объектный тип со всеми свойствами T, кроме ключей, перечисленных в K. Классический пример: тип User, у которого есть password, и публичная версия, у которой его нет.
PublicUser остаётся синхронным с User: добавьте поле в User, и оно появится и в PublicUser, а password останется за бортом. Omit работает только с типами; удаляет свойство из настоящего объекта деструктуризация в toPublic.
Исключение нескольких ключей
Второй аргумент это объединение, поэтому перечисляйте через | столько ключей, сколько нужно:
«Тип без полей, которые генерирует сервер» это самый частый Omit в реальном коде: один тип модели, из которого Omit выводит структуру входных данных для форм создания и запросов к API.
Как определён Omit
Omit построен из двух других служебных типов:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
Читайте изнутри: keyof T это объединение всех ключей, Exclude<keyof T, K> убирает из него ненужные, а Pick оставляет остальные ключи. Поэтому Omit<User, "password"> это Pick<User, "id" | "name" | "email">. Поскольку Pick перебирает ключи T, модификаторы readonly и ? у оставшихся свойств сохраняются.
Обратите внимание на ограничение: K extends keyof any, а не K extends keyof T. Этой детали посвящён следующий раздел.
Omit не проверяет, что ключ существует
keyof any это string | number | symbol, поэтому Omit принимает вообще любой ключ, включая тот, которого нет у T. Опечатка компилируется и ничего не удаляет:
Ошибок нигде нет, а пароль уходит в JSON. Мягкое ограничение выбрано намеренно, когда Omit добавили в TypeScript 3.5. Доводы команды: в обобщённом коде исключаемые ключи часто берутся не из T (функция, которая сливает два объекта, может типизировать свой rest-аргумент как Omit<T1, keyof T2>), Exclude тоже не ограничивает второй параметр, а ограниченная версия сломала бы множество самописных типов Omit, которые уже были в проектах. Для повседневного кода опечатку ловит более строгая версия:
index.ts(10,36): error TS2344: Type '"pasword"' does not satisfy the constraint 'keyof User'.
index.ts(11,7): error TS2741: Property 'password' is missing in type '{ id: number; name: string; }' but required in type 'PublicUser'.
Важна первая ошибка: ключ проверяется по keyof User. Вторая следует из неё, поскольку ничего не исключено и PublicUser по-прежнему требует password. Исправьте написание на "password", и код компилируется. Многие кодовые базы определяют такой тип один раз в общем файле типов и используют его вместо Omit.
Переопределение типа свойства
Интерфейс не может расширить другой и изменить свойство на несовместимый тип: interface ApiUser extends User { id: string } завершается ошибкой error TS2430: Interface 'ApiUser' incorrectly extends interface 'User'. Сначала исключите свойство, затем объявите его снова:
С псевдонимом типа то же самое делается пересечением: type ApiUser = Omit<User, "id"> & { id: string }. Без Omit пересечение User & { id: string } дало бы id тип number & string, то есть never.
Omit для типов объединения
Omit не дистрибутивен. Для объединения он начинает с keyof всего объединения, где есть только ключи, общие для всех членов, поэтому собственные свойства членов исчезают:
index.ts(8,39): error TS2353: Object literal may only specify known properties, and 'radius' does not exist in type 'NewShape'.
NewShape превратился в { kind: "circle" | "square" }. Чтобы исключать из каждого члена отдельно, применяйте Omit внутри дистрибутивного условного типа:
T extends unknown ? ... : never всегда истинно; оно нужно только для того, чтобы условный тип распределился по объединению. Почему это работает, объясняет страница об условных типах. Используйте DistributiveOmit всякий раз, когда тип, из которого вы исключаете, это размеченное объединение.
Исключение вложенного свойства
Omit смотрит только на ключи верхнего уровня, поэтому Omit<Settings, "editor.wordWrap"> ничего не удаляет (и, как показано выше, не жалуется). Чтобы убрать свойство на уровень ниже, исключите родителя и добавьте его обратно с собственным Omit:
Settings["editor"] это тип доступа по индексу: он называет тип вложенного объекта, чтобы Omit мог с ним работать.
Удаление свойств во время выполнения
Omit меняет только тип. Чтобы получить объект без свойств, уберите их деструктуризацией или напишите небольшую обобщённую вспомогательную функцию с типом возврата Omit:
Вспомогательная функция ограничивает K через keyof T, поэтому ключ с опечаткой становится ошибкой в месте вызова. Копия поверхностная: вложенные объекты общие с оригиналом.
Omit, Pick и Exclude
| Работает с | Оставляет | Пример | Результат | |
|---|---|---|---|---|
Omit<T, K> | объектными типами | все свойства, кроме K | Omit<User, "password"> | { id; name; email } |
Pick<T, K> | объектными типами | только свойства из K | Pick<User, "id" | "name"> | { id; name } |
Exclude<U, M> | типами объединения | члены объединения, не присваиваемые в M | Exclude<"a" | "b" | "c", "a"> | "b" | "c" |
Выбирайте между Omit и Pick по тому, что должно происходить, когда исходный тип растёт. Omit автоматически включает новые свойства, это подходит для «всё, кроме секрета». Pick никогда их не включает, это подходит для «ровно эти поля» и безопаснее для всего, что покидает ваш сервер. Pick проверяет свои ключи по keyof T; Omit нет.
Часто задаваемые вопросы
Что делает Omit в TypeScript?
Omit<T, K> строит новый объектный тип со всеми свойствами T, кроме тех, чьи ключи есть в K. Omit<User, "password"> это User без password. Он меняет только тип и ничего не удаляет из объекта во время выполнения.
Как исключить несколько свойств в TypeScript?
Передайте объединение ключей вторым аргументом: Omit<User, "password" | "createdAt" | "updatedAt">. Каждый ключ из объединения удаляется.
Чем Omit отличается от Exclude?
Omit удаляет свойства из объектного типа: Omit<{ a: 1; b: 2 }, "a"> это { b: 2 }. Exclude удаляет члены из типа объединения: Exclude<"a" | "b", "a"> это "b". Omit реализован через Exclude: Pick<T, Exclude<keyof T, K>>.
Почему Omit не выдаёт ошибку для несуществующего ключа?
Его параметр ключей ограничен keyof any (любая строка, число или символ), а не keyof T, поэтому опечатка вроде Omit<User, "pasword"> компилируется и ничего не удаляет. Определите type StrictOmit<T, K extends keyof T> = Omit<T, K>, чтобы получать ошибку для неизвестных ключей.
Как удалить свойство из объекта во время выполнения в TypeScript?
Используйте деструктуризацию с rest-элементом: const { password, ...rest } = user;. TypeScript типизирует rest как объект без password, ту же структуру, что Omit<User, "password">, и в новом объекте свойства действительно нет.