Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке discriminant property?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Discriminant property** - це **спільна властивість**, яка присутня в усіх членів об'єднання (`union`) і приймає **унікальні літеральні значення**, що дозволяє TypeScript відрізняти один варіант типу від іншого. **Ключове:** завдяки дискримінантній властивості TypeScript автоматично звужує тип (narrowing) у кожній гілці коду, що дозволяє створювати `discriminated unions` і безпечні switch-розгалуження.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## Що таке *discriminant property* **Discriminant property** - це **спільна властивість**, яка присутня в усіх членів об'єднання (`union`) і приймає **унікальні літеральні значення**, що дозволяє TypeScript відрізняти один варіант типу від іншого. > Простіше кажучи: дискримінантна властивість - це «мітка», за якою компілятор розуміє, *хто перед ним*. ### Приклад ```javascript type Circle = { kind: "circle"; radius: number; }; type Square = { kind: "square"; side: number; }; type Shape = Circle | Square; ``` Тут властивість `kind` - це **discriminant property**. Вона: - присутня **в обох типах**, - приймає **унікальні літеральні значення** (`"circle"`, `"square"`), - і саме за нею TypeScript відрізняє, який тип використовується зараз. ### Використання ```javascript function area(shape: Shape) { if (shape.kind === "circle") { // shape: Circle return Math.PI * shape.radius ** 2; } else { // shape: Square return shape.side ** 2; } } ``` TypeScript бачить, що `kind` - це дискримінатор, і **автоматично звужує тип (narrowing)** у кожній гілці. ## Як TypeScript «розпізнає» дискримінатор Щоб властивість вважалася **дискримінантною (discriminant)**, мають виконуватися три умови. | Умова | Пояснення | |---|---| | 1 | Усі варіанти об'єднання (`union`) мають бути **об'єктними типами** | | 2 | У всіх має бути **однойменний ключ** (наприклад, `kind`, `type`, `status`) | | 3 | Цей ключ має мати **літеральне значення** (`"circle"`, `"square"`, `"error"`), а не просто `string` | Приклад: ```javascript type Loading = { status: "loading" }; type Success = { status: "success"; data: string }; type Error = { status: "error"; message: string }; type Response = Loading | Success | Error; ``` Приклад (не буде дискримінованим): ```javascript type Bad = { status: string; data?: string }; type Worse = { status: string; message?: string }; type InvalidUnion = Bad | Worse; // TS не зможе звужувати за status ``` ## Приклад з `switch` і exhaustiveness check ```javascript function handle(response: Response) { switch (response.status) { case "loading": return "Loading..."; case "success": return `Data: ${response.data}`; case "error": return `Error: ${response.message}`; default: const _exhaustive: never = response; return _exhaustive; } } ``` TypeScript знає, що `status` - це дискримінатор, і автоматично звузить тип у кожному `case`. Якщо додати новий варіант, але не обробити його, компілятор попередить про помилку через `never`. ## Типові назви дискримінаторів Властивість можна назвати як завгодно, але на практиці найчастіше використовують такі назви. | Назва | Приклад значення | |---|---| | `kind` | `"circle"`, `"square"` | | `type` | `"add"`, `"remove"`, `"reset"` | | `status` | `"loading"`, `"success"`, `"error"` | | `variant` | `"light"`, `"dark"` | | `tag` | `"left"`, `"right"` | | `mode` | `"auto"`, `"manual"` | ## Реальні кейси застосування ### Redux-подібні actions ```javascript type Action = | { type: "add"; payload: number } | { type: "remove"; id: string } | { type: "reset" }; function reducer(state: number[], action: Action) { switch (action.type) { case "add": return [...state, action.payload]; case "remove": return state.filter((_, i) => i.toString() !== action.id); case "reset": return []; } } ``` `type` - дискримінантна властивість. ### Стани завантаження ```javascript type State = | { status: "idle" } | { status: "loading" } | { status: "success"; data: string } | { status: "error"; error: string }; function render(state: State) { if (state.status === "loading") return "Loading..."; if (state.status === "error") return `${state.error}`; if (state.status === "success") return `${state.data}`; return "Idle"; } ``` `status` - дискримінантна властивість. ## Чому це так важливо Без дискримінатора TypeScript не знає, як розрізняти члени об'єднання: ```javascript type A = { name: string }; type B = { age: number }; type AB = A | B; function f(v: AB) { if ("name" in v) { // TS не може точно сказати, що v - це A console.log(v.name); } } ``` З дискримінатором усе стає **строго типізованим і безпечним**: ```javascript type A = { type: "A"; name: string }; type B = { type: "B"; age: number }; type AB = A | B; function f(v: AB) { if (v.type === "A") { console.log(v.name); // TS знає, що v - це A } } ``` ## Підсумок | Поняття | Опис | |---|---| | **Discriminant property** | Спільна властивість усіх членів union-типу, яка має унікальне літеральне значення | | **Навіщо потрібна** | Щоб TypeScript міг автоматично звужувати тип в union | | **Типові назви** | `kind`, `type`, `status`, `variant`, `tag` | | **Ключова роль** | Дозволяє створювати «discriminated unions» і робити безпечні switch-розгалуження | | **Головна умова** | Значення властивості мають бути літеральними та унікальними | --- **Просте порівняння:** > *Discriminant property* - це як «паспорт» об'єкта: > TypeScript дивиться в поле `kind` і одразу знає, до якого «громадянства» (типу) він належить.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.