Що таке "discriminated union"?
Що таке discriminated union?
Discriminated union - це об'єднання (union) об'єктів, у яких є
спільна властивість-мітка (яку називають discriminant або tag),
значення якої однозначно визначає тип об'єкта.
Це дозволяє TypeScript розпізнавати, який саме варіант типу використовується, і автоматично звужувати тип (type narrowing) під час перевірок.
Простий приклад
type Circle = {
kind: "circle";
radius: number;
};
type Square = {
kind: "square";
side: number;
};
type Shape = Circle | Square;Тут:
Shape- об'єднання (Circle | Square);- поле
kind- дискримінатор, воно розрізняє варіанти.
Використання з type narrowing
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 бачить:
- об'єднання (
Circle | Square), - спільну властивість (
kind), - і унікальні рядкові літерали в цій властивості.
Після перевірки shape.kind === "circle",
компілятор знає, що в цьому контексті shape точно Circle.
Приклад з кількома варіантами
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)
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
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
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
| Звичайний union | Discriminated 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 |
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.