Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Утиліта type omit в TypeScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Omit<T, K>** - утилітний тип TypeScript, який створює новий тип, видаляючи ключі `K` з типу `T`. ```typescript interface User { id: string; name: string; email: string; password: string; } type PublicUser = Omit<User, "password">; // { id: string; name: string; email: string } ``` **Ключове:** використовуй `Omit`, коли потрібна більшість полів типу, але кілька треба виключити, наприклад прибрати password перед відправкою API-відповіді.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Omit<T, K>** - утилітний тип, який бере тип `T` і повертає його копію без ключів, перелічених у `K`. ## Теорія ### TL;DR - Уяви форму, де певні поля замальовані маркером: решта залишається, тільки вказані поля зникають - Головна різниця від `Pick`: `Omit` віднімає ключі від повного типу; `Pick` будує новий тип, вибираючи тільки те, що ти назвав - Правило вибору: виключень менше, ніж залишків? Бери `Omit`. Потрібна мала підмножина? Бери `Pick` - Жодного впливу на рантайм: стирається під час компіляції у JavaScript - Працює тільки на верхньому рівні ключів; шляхи на кшталт `"address.zip"` не мають жодного ефекту ### Швидкий приклад ```typescript interface User { id: string; name: string; email: string; password: string; } type PublicUser = Omit<User, "password">; // Результат: { id: string; name: string; email: string } // password зник, решта без змін ``` Два рядки коду типів, одне прибране поле. Ось і вся ідея. ### Ключова різниця `Pick<T, K>` будує тип з нуля, вибираючи тільки ті ключі, що ти перелічив. `Omit<T, K>` стартує від повного типу і видаляє перелічені ключі, зберігаючи все інше. Якщо тип має 10 властивостей і тобі потрібні 9 з них, `Omit` рятує від необхідності перераховувати всі 9 у `Pick`. ### Коли використовувати - API-відповідь без чутливих полів: `Omit<User, "password" | "internalId">` - Props React-компонента без внутрішніх callback-ів: `Omit<ComponentProps, "onInternalClick">` - Сутність бази даних, перетворена на DTO: `Omit<Entity, "createdBy" | "updatedAt">` - Тип для форми створення запису: `Omit<Product, "id" | "createdAt">` ### Порівняння Omit vs Pick | Утиліта | Що робить | Коли підходить | |---|---|---| | `Pick<T, K>` | Залишає тільки названі ключі | Мала підмножина великого типу (2-3 поля) | | `Omit<T, K>` | Видаляє названі ключі, решту залишає | Потрібна більшість полів, виключити треба кілька | | **Правило вибору** | | Виключень менше, ніж залишків? `Omit`. Інакше `Pick` | Ці два типи можуть давати однаковий результат: ```typescript interface User { id: string; name: string; email: string; password: string; createdAt: Date; } type A = Pick<User, "id" | "name" | "email">; type B = Omit<User, "password" | "createdAt">; // A і B структурно ідентичні ``` Обирай той варіант, який краще передає намір у конкретному місці коду. ### Як компілятор обробляє Omit TypeScript розгортає `Omit<T, K>` через `Pick` і `Exclude`: ```typescript // Вбудоване визначення TypeScript: type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>; // Крок за кроком для Omit<User, "password">: // 1. keyof User = "id" | "name" | "email" | "password" | "createdAt" // 2. Exclude<keyof User, "password"> = "id" | "name" | "email" | "createdAt" // 3. Pick<User, "id" | "name" | "email" | "createdAt"> = результат ``` Жодного впливу на рантайм. Після компіляції у JavaScript від цього нічого не залишається. Ще один нюанс: `Omit` зберігає індексні сигнатури. Якщо тип має `[key: string]: unknown`, вона залишиться в результаті навіть після видалення конкретних ключів. ### Типові помилки **1. Omit ключа, якого не існує** ```typescript interface User { id: string; name: string; } type Wrong = Omit<User, "nonExistent">; // Компілюється без помилок. Жодного ефекту. // TypeScript ігнорує ключі в K, яких немає в T. ``` Жодного краша, жодного попередження. Тип залишається таким самим, а ти витрачаєш час розбираючись, чому нічого не змінилося. **2. Очікування що Omit видаляє вкладені ключі** ```typescript type UserWithAddress = { name: string; address: { street: string; zip: string }; }; // Це не має жодного ефекту: type Wrong = Omit<UserWithAddress, "address.zip">; // Результат: { name: string; address: { street: string; zip: string } } ``` `Omit` поверхневий. Він видаляє тільки ключі верхнього рівня. Щоб прибрати вкладене поле, потрібно вручну перебудувати вкладений тип або написати рекурсивну утиліту. **3. Використання Omit з union-типами без дистрибуції** ```typescript type Union = string | { id: string; secret: string }; type Attempt = Omit<Union, "secret">; // Результат не буде таким, як очікується. // Omit автоматично не розподіляється по членам union. ``` Для union-типів застосовуй `Omit` до кожного члена окремо. **4. Повернення видаленого ключа через extends** ```typescript type PublicUser = Omit<User, "password">; // Це компілюється: interface AdminUser extends PublicUser { password: string; // password повернувся } ``` Структурна типізація це дозволяє. Якщо мета була не пустити `password` у тип взагалі, цей підхід не захистить. Використовуй перетин типів (intersection) або окрему базу. **5. Плутанина між Omit та Exclude** `Exclude<keyof T, K>` повертає union з іменами ключів, що залишилися. `Omit<T, K>` повертає новий тип об'єкта з видаленими ключами. Різні результати, різні сценарії. ### Де зустрічається - **React**: `Omit<ComponentProps<'input'>, 'ref'>` в обгортках `forwardRef` (патерн з типів React 18) - **Zod**: `Omit<z.infer<typeof userSchema>, 'password'>` для публічних схем - **Prisma**: `Omit<Prisma.UserGetPayload<{}>, 'passwordHash'>` у типах API-відповідей - **tRPC**: вхідні дані процедур через `Omit<FullInput, 'internalToken'>` - **Express**: звуження типів тіла запиту в middleware На практиці `Omit` найчастіше зустрічається на межах API, де потрібно прибрати чутливі або серверні поля перед відправкою даних клієнту. Цей патерн покриває приблизно 70% випадків реального використання у кодових базах. ### Питання для поглиблення **Q:** Що відбувається, якщо `K` містить ключ, якого немає в `T`? **A:** TypeScript компілюється без помилок і ігнорує невідомий ключ. Результуючий тип ідентичний `T`. **Q:** Як `Omit` поводиться з індексними сигнатурами типу `[key: string]: unknown`? **A:** Зберігає їх. `Omit<{ [k: string]: any; foo: string }, 'foo'>` залишає індексну сигнатуру і видаляє тільки `foo`. **Q:** Яка різниця між `Omit<T, K>` і `Exclude<keyof T, K>`? **A:** `Exclude` повертає union імен ключів. `Omit` повертає повний тип об'єкта з видаленими ключами. Перше, це рядковий union, друге, це готовий тип для використання. **Q:** Як реалізувати `Omit` самостійно? **A:** `type MyOmit<T, K extends keyof any> = { [P in keyof T as P extends K ? never : P]: T[P] }`. Клауза `as` у mapped type фільтрує ключі, що збігаються з `K`, перемапуючи їх у `never`. **Q:** Як `Omit` поводиться з intersection types на кшталт `A & B`? **A:** Дистрибутивно: `Omit<A & B, K>` приблизно дорівнює `Omit<A, K> & Omit<B, K>`. Точний результат залежить від того, у якому саме типі перетину міститься цей ключ. ## Приклади ### Базовий: видалення чутливого поля ```typescript interface User { id: string; name: string; email: string; password: string; } type PublicUser = Omit<User, "password">; // { id: string; name: string; email: string } function getPublicUser(user: User): PublicUser { const { password, ...rest } = user; return rest; // TypeScript знає, що це відповідає PublicUser } ``` Паттерн деструктуризації зі spread природно поєднується з `Omit`: тип описує очікувану структуру, рантайм прибирає реальне значення. ### Середній рівень: DTO-типи для CRUD API ```typescript interface Product { id: string; name: string; price: number; description: string; createdAt: Date; updatedAt: Date; } // POST /products - без id і часових міток (їх встановлює сервер) type CreateProductDTO = Omit<Product, "id" | "createdAt" | "updatedAt">; // PATCH /products/:id - id береться з URL, всі поля необов'язкові type UpdateProductDTO = Partial<Omit<Product, "id">>; // Ці типи запобігають випадковій передачі серверних полів ``` `Omit` у поєднанні з `Partial` покриває більшість CRUD-операцій. Визначаєш вихідний тип один раз і деривуєш решту з нього. ### Просунутий рівень: props React-компонента з forwardRef ```typescript import { forwardRef, InputHTMLAttributes } from "react"; interface CustomInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "ref"> { label: string; error?: string; } const CustomInput = forwardRef<HTMLInputElement, CustomInputProps>( ({ label, error, ...props }, ref) => ( <div> <label>{label}</label> <input ref={ref} {...props} /> {error && <span>{error}</span>} </div> ) ); ``` `Omit<InputHTMLAttributes<HTMLInputElement>, 'ref'>` прибирає вбудований `ref`, щоб `forwardRef` міг керувати ним напряму. Саме такий патерн використовується у типах React 18.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.