Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Обов'язковий тип утиліти в TypeScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Required<T>** - утилітний тип TypeScript, який видаляє `?` з кожної необов'язкової властивості `T`, роблячи всі поля обов'язковими. ```ts interface User { name?: string; age?: number; } type StrictUser = Required<User>; // { name: string; age: number; } ``` **Головне:** Протилежність `Partial<T>`. Працює тільки на рівні компіляції.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Required<T>** - утилітний тип TypeScript, який знімає `?` з кожної необов'язкової властивості типу `T`, роблячи їх усі обов'язковими на рівні компіляції. ## Теорія ### TL;DR - Уявіть анкету, де всі поля були необов'язковими - і раптом на кожному з'явився штамп "заповнити обов'язково" - Головна різниця: знімає `?` з кожної властивості, але тип не змінює (`string | undefined` стає просто `string`) - Використовуй після валідації, коли знаєш що всі поля є; `Partial<T>` залишай на етапі збору даних - Працює лише під час компіляції - у рантаймі жодного ефекту - Протилежність `Partial<T>` ### Швидкий приклад ```ts interface User { name: string; age?: number; // необов'язково } type StrictUser = Required<User>; const user: StrictUser = { name: "Alice", age: 30 }; // працює // const bad: StrictUser = { name: "Bob" }; // Помилка: age відсутній ``` `StrictUser` тепер вимагає і `name`, і `age`. Пропусти будь-яке - TypeScript відразу повідомить про помилку. ### Головна різниця `Required<T>` тільки знімає модифікатор `?` - і нічого більше. `age?: number` стає `age: number`, а не `age: number | undefined`. Це важливо, коли пишеш функції що очікують гарантоване значення: TypeScript ловить помилки під час компіляції, а не в рантаймі. ### Коли використовувати - Дані збирались як `Partial<T>`, потім валідувались: використовуй `Required<T>` як тип повернення функції валідації - Відповідь від API після успішного запиту: структура вже повна - Props React-компонента з усіма значеннями за замовчуванням: передай `Required<Props>` дочірньому компоненту - Генерація моків: уникнеш несподіваних `undefined` у тестах - Після type guard, що підтвердив наявність усіх полів: `data is Required<FormData>` читається зрозуміло ### Як це обробляє компілятор TypeScript перебирає дескриптори кожної властивості `T`. Якщо властивість позначена як необов'язкова (прапорець `?`), компілятор переписує її в обов'язкову. Під капотом це mapped type: `{ [K in keyof T]-?: T[K] }`. Синтаксис `-?` знімає модифікатор необов'язковості. JavaScript при цьому не змінюється - суто перевірка на рівні компіляції. ### Поширені помилки **1. Очікування перевірки в рантаймі** ```ts // Required<T> НЕ перевіряє значення в рантаймі const data = JSON.parse(response) as Required<User>; // обходить усі перевірки ``` TypeScript довіряє касту. Якщо потрібна перевірка в рантаймі, поєднуй `Required<T>` з type guard або схемним валідатором на кшталт Zod. **2. Очікування що він обробляє вкладені необов'язкові поля** Ця поверхневість ловить майже всіх з першого разу. Застосовуєш `Required<T>` очікуючи повного покриття, а потім годину відлагоджуєш `undefined` у конфіг-об'єкті на третьому рівні вкладеності. ```ts type Nested = Required<{ a?: { b?: string } }>; // Результат: { a: { b?: string } } // a стало обов'язковим, але b всередині залишається необов'язковим ``` Для вкладених структур пиши рекурсивний тип: ```ts type DeepRequired<T> = T extends object ? { [K in keyof T]-?: DeepRequired<T[K]> } : T; ``` **3. Застосування до union-типів** ```ts type Bad = Required<string | { a?: number }>; // Поводиться несподівано - уникай Required на union-типах напряму ``` Якщо тип - union, застосовуй `Required` до кожного члена окремо або спочатку використовуй `Extract`. **4. Застосування до примітивів** `Required<string>` повертає `string` без змін. Знімати нічого. ### Де зустрічається - **Обробник форми в React**: `validateForm(data: FormData): data is Required<FormData>` - type guard після перевірки всіх полів - **Express middleware**: `Request<{}, {}, Required<CreateUserBody>>` після валідації тіла запиту - **React Query**: `Required<Omit<Response, 'data'>>` на шляху успіху - **Zod**: `z.infer<typeof schema> & Required<PartialFields>` для часткових схем - **TanStack Table**: об'єднання column def через `Required<Partial<ColumnDef>>` ### Follow-up питання **Q:** Який тип у `Required<{ a?: string }>`? **A:** `{ a: string }`. Знак `?` знімається, тип залишається `string`, а не `string | undefined`. **Q:** Чи впливає `Required<T>` на вкладені об'єкти? **A:** Ні. Він знімає `?` тільки з властивостей верхнього рівня. Вкладені необов'язкові поля залишаються необов'язковими. Для вкладених структур використовуй кастомний `DeepRequired<T>`. **Q:** У чому різниця між `Required<T>` і `Omit<T, never>`? **A:** Структурний результат однаковий, але `Required<T>` - це семантичний вибір. `Omit<T, never>` є обхідним шляхом і ламається на не-об'єктних типах. **Q:** Як зробити `Required` тільки для конкретних ключів? **A:** `type RequiredSpecific<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>`. Поєднує `Omit` і `Pick` щоб точно вибрати лише потрібні ключі. **Q:** Чому в mapped types є синтаксис `-?`? **A:** Це синтаксис модифікатора. `+?` додає необов'язковість, `-?` знімає її. `Required<T>` використовує `{ [K in keyof T]-?: T[K] }` під капотом. ## Приклади ### Базовий: робимо необов'язкові поля обов'язковими ```ts interface Product { id: number; name: string; description?: string; price?: number; } type FullProduct = Required<Product>; const item: FullProduct = { id: 1, name: "Keyboard", description: "Mechanical", // тепер обов'язково price: 99, // тепер обов'язково }; // const broken: FullProduct = { id: 1, name: "Mouse" }; // Помилка: поля відсутні ``` До `Required<T>` можна було створити `Product` без `description` і `price`. Після - TypeScript не дозволить на рівні типів. ### Середній рівень: обробник валідації форми з type guard ```ts interface FormData { email: string; phone?: string; address?: string; } function validateForm(data: FormData): data is Required<FormData> { return !!data.phone && !!data.address; } function submitUser(formData: FormData) { if (validateForm(formData)) { // TypeScript знає що всі поля є всередині цього блоку console.log(`${formData.email} - ${formData.phone} - ${formData.address}`); } } const data: FormData = { email: "user@example.com", phone: "555-1234", address: "10 Main St", }; submitUser(data); ``` Type guard `data is Required<FormData>` звужує тип всередині блоку `if`. Ніякого кастингу. Цей патерн часто зустрічається в API-роутах Next.js після збору та перевірки даних форми.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.