Skip to main content

Що таке "discriminated union"?

Що таке discriminated union?

Discriminated union - це об'єднання (union) об'єктів, у яких є спільна властивість-мітка (яку називають discriminant або tag), значення якої однозначно визначає тип об'єкта.

Це дозволяє TypeScript розпізнавати, який саме варіант типу використовується, і автоматично звужувати тип (type narrowing) під час перевірок.


Простий приклад

javascript
type Circle = { kind: "circle"; radius: number; }; type Square = { kind: "square"; side: number; }; type Shape = Circle | Square;

Тут:

  • Shape - об'єднання (Circle | Square);
  • поле kind - дискримінатор, воно розрізняє варіанти.

Використання з type narrowing

javascript
function area(shape: Shape): number { if (shape.kind === "circle") { // тут shape: Circle return Math.PI * shape.radius ** 2; } else { // тут shape: Square return shape.side ** 2; } }

TypeScript автоматично визначає, що в кожному блоці if/else тип звузився до потрібного варіанта (Circle чи Square).


Як це працює

TypeScript бачить:

  1. об'єднання (Circle | Square),
  2. спільну властивість (kind),
  3. і унікальні рядкові літерали в цій властивості.

Після перевірки shape.kind === "circle", компілятор знає, що в цьому контексті shape точно Circle.


Приклад з кількома варіантами

javascript
type Shape = | { kind: "circle"; radius: number } | { kind: "square"; side: number } | { kind: "triangle"; base: number; height: number }; function getArea(shape: Shape) { switch (shape.kind) { case "circle": return Math.PI * shape.radius ** 2; case "square": return shape.side ** 2; case "triangle": return (shape.base * shape.height) / 2; default: const _exhaustive: never = shape; // перевірка на повноту return _exhaustive; } }

Тут:

  • switch автоматично звузить тип за kind.
  • never у default гарантує, що всі варіанти оброблено.

Переваги discriminated union

1. Безпечне розгалуження - TS гарантує, що ти обробив усі випадки. 2. Без as і кастів - тип визначається автоматично. 3. Чудово працює зі switch, if і pattern-подібними перевірками. 4. Підходить для опису станів, дій, помилок, подій.


Реальні приклади застосування

1. Стани завантаження (state machine pattern)

javascript
type LoadingState = { status: "loading" }; type SuccessState = { status: "success"; data: string }; type ErrorState = { status: "error"; error: Error }; type RequestState = LoadingState | SuccessState | ErrorState; function render(state: RequestState) { switch (state.status) { case "loading": return "Loading..."; case "success": return `Data: ${state.data}`; case "error": return `Error: ${state.error.message}`; } }

2. Дії у стилі Redux

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 []; } }

3. Відповіді API

javascript
type ApiResponse<T> = | { kind: "ok"; data: T } | { kind: "error"; message: string }; function handleResponse(res: ApiResponse<string>) { if (res.kind === "ok") { console.log("OK:", res.data); } else { console.error("Error:", res.message); } }

Відмінність від звичайного union

Звичайний unionDiscriminated union
type A = {a: number} | {b: string}type A = { kind: "x"; a: number } | { kind: "y"; b: string }
Під час перевірки властивостей TypeScript не знає, який це варіантTypeScript автоматично звужує тип за kind
Потрібно робити перевірки вручну ('a' in obj)Достатньо порівняти мітку (obj.kind === 'x')

Підсумок

ПоняттяОпис
Discriminant (мітка)Спільна властивість з унікальними літеральними значеннями, що визначає варіант типу
Discriminated unionОб'єднання типів, розрізнюваних за міткою
Головна перевагаTypeScript сам звужує тип залежно від значення мітки
Типове застосуванняСтани завантаження, actions, варіанти результату, finite state machines

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.