Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Коли відбувається звуження типів?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Звуження типів (type narrowing)** відбувається, коли TypeScript отримує **достатньо інформації**, щоб **виключити деякі варіанти** з об'єднаного або невизначеного типу. **Ключове:** воно спрацьовує на перевірках `typeof`, `instanceof`, `in`, порівняннях `===`/`!==`, truthy/falsy перевірках, користувацьких type guards, дискримінаторах об'єднань і аналізі потоку керування (`return`, `throw`), а після виходу з гілки TypeScript знову об'єднує можливі типи.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## Ключова ідея > **Звуження типів (type narrowing)** відбувається, > коли TypeScript отримує **достатньо інформації**, щоб **виключити деякі варіанти** з об'єднаного або невизначеного типу. Тобто: - У нас є змінна з "широким" типом (`string | number | null | undefined | ...`), - Відбувається якась **логічна умова або перевірка**, - TypeScript робить висновок: "Раз виконано цю умову, значить залишилися лише такі варіанти типу". --- ## Коли саме відбувається narrowing TypeScript **автоматично звужує тип** за таких подій: | Категорія | Механізм | Приклад | |---|---|---| | **1. Перевірки через оператори** | `typeof`, `instanceof`, `in` | `if (typeof x === "string")` | | **2. Перевірки рівності / нерівності** | `===`, `!==`, `==`, `!=` | `if (x === null)` | | **3. Перевірки truthy / falsy** | `if (x)`, `!x`, `x && y`, `x | | | **4. Користувацькі type guards** | `value is Type` | `if (isString(val))` | | **5. Перевірки дискримінатора (discriminated unions)** | `if (obj.kind === "circle")` або `switch (obj.status)` | | | **6. Управління потоком (control flow)** | `return`, `throw`, `break`, `continue` | після `return` решта типів виключається | | **7. Логічні вирази з типами** | `!x`, `x && y`, `x ? a : b` | у кожній гілці свій звужений тип | | **8. Використання** `never` **після повного виключення** | якщо TS "розуміє", що інших типів не залишилось | `default:` у `switch` після всіх випадків | --- ## Розглянемо кожну категорію на прикладах --- ### 1. `typeof`, `instanceof`, `in` ```javascript function example(value: string | number | boolean) { if (typeof value === "string") { // value: string } else if (typeof value === "number") { // value: number } else { // value: boolean } } ``` ```javascript if (x instanceof Date) { // x: Date } ``` ```javascript if ("length" in obj) { // obj: { length: number } або подібне } ``` --- ### 2. Перевірки рівності ```javascript function greet(name: string | null) { if (name === null) { console.log("No name"); } else { // name: string console.log("Hello, " + name.toUpperCase()); } } ``` Після `if (name === null)` TS **виключає** `null` з об'єднання в гілці `else`. --- ### 3. Truthy / Falsy перевірки ```javascript function process(user?: { name: string } | null) { if (!user) { // user: null | undefined return; } // Тут user: { name: string } console.log(user.name); } ``` Будь-яка перевірка "на порожнечу" (`if (x)`) автоматично прибирає `null` і `undefined`. --- ### 4. Користувацькі Type Guards ```javascript function isString(value: unknown): value is string { return typeof value === "string"; } function print(value: unknown) { if (isString(value)) { // value: string console.log(value.toUpperCase()); } } ``` TS довіряє функції `isString` - якщо вона повертає `true`, значить всередині цієї гілки тип `value` точно `string`. --- ### 5. Дискриміновані об'єднання ```javascript type Shape = | { kind: "circle"; radius: number } | { kind: "square"; side: number }; function area(shape: Shape) { if (shape.kind === "circle") { // shape: { kind: "circle"; radius: number } return Math.PI * shape.radius ** 2; } else { // shape: { kind: "square"; side: number } return shape.side ** 2; } } ``` TS **звужує тип за дискримінатором** (`kind`). --- ### 6. Управління потоком ```javascript function example(x: string | null | undefined) { if (x == null) return; // виключає null і undefined // Тут x: string console.log(x.toUpperCase()); } ``` TypeScript бачить, що після `return` "гілка" з `null`/`undefined` відсікається, і далі залишаються лише інші варіанти. --- ### 7. Логічні вирази ```javascript function showName(user?: { name: string } | null) { // user?.name звужує тип всередині const name = user && user.name; // user: { name: string } | null | undefined if (name) { // name: string console.log(name.toUpperCase()); } } ``` --- ### 8. Звуження до `never` ```javascript function handle(val: "a" | "b") { switch (val) { case "a": return "Got A"; case "b": return "Got B"; default: const _exhaustive: never = val; // TS гарантує, що сюди не потрапити return _exhaustive; } } ``` TypeScript розуміє, що після обробки всіх можливих випадків тип звузився до `never` - більше немає варіантів. --- ## Коли звуження *не відбувається* 1. Якщо тип занадто широкий (`any`) - TS не звужує: ```javascript let value: any; if (typeof value === "string") { // value: any - TS нічого не знає } ``` 2. Якщо перевірка не дає однозначної інформації: ```javascript let v: string | number; if (v) { // все ще string | number } ``` 3. Якщо TS не може відстежити контроль потоку (наприклад, при мутації змінної). --- ## У який момент компіляції це відбувається TypeScript виконує **control flow analysis (CFA)** - аналіз шляху виконання програми, крок за кроком: 1. Сканує кожну інструкцію (`if`, `return`, `throw`, `try`, `switch`); 2. Будує "граф станів типів" (type flow graph); 3. У кожній гілці зберігає "поточний тип змінної"; 4. Після виходу з блока - об'єднує можливі типи назад (union merge). --- ### Приклад: як думає TypeScript ```javascript let x: string | number | null; if (x === null) { // x: null } else if (typeof x === "string") { // x: string } else { // x: number } ``` Внутрішньо TS робить приблизно так: ```javascript Вихідний тип: string | number | null -> if (x === null): гілка 1 = null -> else: гілка 2 = string | number -> if (typeof x === "string"): гілка 2a = string -> else: гілка 2b = number ``` --- ## Підсумок | Питання | Відповідь | |---|---| | **Коли відбувається narrowing?** | Коли TypeScript бачить логічну перевірку, яка виключає частину можливих типів | | **Що саме він робить?** | "Відкидає" типи, які неможливі в поточній гілці | | **На основі чого?** | `typeof`, `instanceof`, `in`, `===`, `==`, `if (x)`, `return`, `throw`, `type guards`, дискримінатори | | **Коли припиняється?** | Після виходу з гілки - TS знову об'єднує всі можливі типи | | **Ключова особливість** | Це *автоматичний аналіз потоку виконання (CFA)*, вбудований у компілятор |Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.