Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Утилітарний тип nonnullable в TypeScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`NonNullable<T>`** прибирає `null` та `undefined` з типу `T`. ```typescript type MaybeId = string | null | undefined; type Id = NonNullable<MaybeId>; // string ``` **Головне:** працює тільки на рівні типів, у runtime нічого не відбувається. Завжди додавай перевірку на null перед використанням звуженого типу.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`NonNullable<T>`** прибирає `null` та `undefined` з типу `T`, залишаючи тільки значення, які там реально можуть бути. ## Теорія ### TL;DR - Уяви кавовий фільтр: заливаєш тип з `null` і `undefined` всередині, а виходить тільки те, що потрібно. - Реалізація: `T extends null | undefined ? never : T` - TypeScript розкладає це по кожному члену union-у. - Використовуй після перевірки на null (`if (!user) return`), а не замість неї. - Прибирає тільки `null`/`undefined` на верхньому рівні. Вкладені null всередині об'єктів залишаються. - Семантично ідентичний до `Exclude<T, null | undefined>`, але назва краще передає намір. ### Короткий приклад ```typescript type MaybeUser = { name: string } | null | undefined; type User = NonNullable<MaybeUser>; // { name: string } // На практиці: після перевірки на null function greet(user: User) { console.log(user.name); // optional chaining не потрібен } ``` Тип каже TypeScript: "до моменту, коли значення потрапляє в цю функцію, null вже оброблений зовні." ### Головна відмінність від Exclude `NonNullable<T>` і `Exclude<T, null | undefined>` дають однаковий результат для звичайних union-ів. Різниця в намірі. `NonNullable` у code review читається як "я гарантую, що тут немає null", а `Exclude` виглядає як загальне віднімання. Обирай `NonNullable`, коли конкретна мета - прибрати null і undefined. ### Коли використовувати - Після runtime-перевірки: зробив `if (!data) return;`, далі функція приймає `NonNullable<typeof data>`. - Необов'язкові поля інтерфейсу: `NonNullable<User["email"]>` дає `string` замість `string | undefined`. - Фільтрація масивів з правильним звуженням типів (приклад нижче). - Після валідації API-відповіді, коли вже впевнений що значення є. Не загортай початкові визначення типів у `NonNullable`. Якщо проп може бути null, так і пиши через `| null`. `NonNullable` - для звуженої, post-check сторони. ### Як це працює всередині TypeScript обчислює `NonNullable<string | null | undefined>`, розподіляючи умовний тип (conditional type) по кожному члену union-у. `string extends null | undefined` - хибно, тому залишається. `null extends null | undefined` - істинно, тому стає `never`. А `never` автоматично випадає з union-ів. Все це стирається до того, як TypeScript генерує JavaScript. ### Типові помилки **Очікування глибокого очищення від null:** ```typescript type HasInnerNull = { prop: string | null }; type Result = NonNullable<HasInnerNull | null>; // Result: { prop: string | null } <- вкладений null залишився! ``` Прибирається тільки верхньорівневий `null`. Для глибокого очищення пиши рекурсивний `DeepNonNullable` або використовуй Zod для runtime-валідації. **Заміна runtime-перевірки:** ```typescript function bad(user: User | null) { const safe: NonNullable<typeof user> = user!; return safe.name; // компілюється, але падає при null у runtime } ``` `NonNullable` існує тільки на рівні типів. Завжди додавай справжній guard перед тим, як використовувати звужений тип. **Застосування там, де нічого прибирати:** ```typescript type Pointless = NonNullable<string>; // все одно string ``` Помилки немає, але це зайвий шум. Використовуй тільки коли тип справді містить `null` або `undefined`. ### Де зустрічається в реальному коді - React Query: після `if (!data) return null;` передаєш `NonNullable<typeof data>` у дочірні компоненти. - Express: як тільки `req.user` заповнений auth-middleware, в обробниках маршрутів використовуй `NonNullable<Request["user"]>`. - Next.js API routes: `req.body` з типом `ParsedBody | null` стає `NonNullable<typeof req.body>` після перевірки. - Необов'язкові поля інтерфейсу: отримуєш чистий `string` з `string | null | undefined` однією утилітою. ### Питання на співбесіді **Q:** Напиши реалізацію `NonNullable<T>`. **A:** `type NonNullable<T> = T extends null | undefined ? never : T;`. У стандартній бібліотеці TypeScript є також варіант через `T & {}`, результат однаковий. **Q:** Яка різниця між `NonNullable<T>` і `Exclude<T, null | undefined>`? **A:** Для union-типів результат ідентичний. `NonNullable` краще підходить, коли мета - конкретно прибрати nullability, бо назва одразу говорить про намір. **Q:** Чи прибирає `NonNullable` null із вкладених полів об'єкта? **A:** Ні. Тільки верхньорівневі `null` і `undefined` з union-у. `NonNullable<{ val: string | null } | null>` дасть `{ val: string | null }`. Для глибокого очищення потрібен рекурсивний mapped type. **Q:** Як `NonNullable` взаємодіє з branded типами? **A:** Бренд зберігається. `NonNullable<(string & { readonly _brand: "UserId" }) | null>` поверне брендований тип без змін, бо бренд не розширює `null | undefined`. ## Приклади ### Вилучення non-null із необов'язкових полів ```typescript interface User { id: string; name: string; email?: string; // string | undefined phone?: string | null; // string | null | undefined } type RequiredEmail = NonNullable<User["email"]>; // string type RequiredPhone = NonNullable<User["phone"]>; // string function sendConfirmation(email: RequiredEmail) { console.log(`Sending to ${email}`); // null-перевірка тут не потрібна } ``` `User["email"]` розкривається до `string | undefined`. Після `NonNullable` отримуємо просто `string`. Стандартний патерн: передаєш необов'язкове поле у функцію, яка чекає на реальне значення. ### Фільтрація масиву з правильним звуженням типу ```typescript const users: (User | null | undefined)[] = [ { id: "1", name: "Alice", email: "alice@example.com" }, null, { id: "2", name: "Bob", email: "bob@example.com" }, undefined, ]; // filter(Boolean) прибирає null/undefined під час виконання, // але TypeScript все одно виводить (User | null | undefined)[] // Type predicate виправляє це: const validUsers = users.filter( (u): u is NonNullable<typeof u> => u != null ); // validUsers: User[] validUsers.forEach(u => console.log(u.name)); // Alice, Bob ``` Предикат `u is NonNullable<typeof u>` - це те, що звужує тип. Без нього TypeScript зберігає nullable union навіть після фільтрації. Цей патерн корисний, коли очищаєш масив з API-відповіді, де можуть зустрічатись null-значення.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.