Що таке discriminant property?
Що таке discriminant property
Discriminant property - це спільна властивість, яка присутня в усіх членів об'єднання (union) і приймає унікальні літеральні значення, що дозволяє TypeScript відрізняти один варіант типу від іншого.
Простіше кажучи: дискримінантна властивість - це «мітка», за якою компілятор розуміє, хто перед ним.
Приклад
type Circle = {
kind: "circle";
radius: number;
};
type Square = {
kind: "square";
side: number;
};
type Shape = Circle | Square;Тут властивість kind - це discriminant property.
Вона:
- присутня в обох типах,
- приймає унікальні літеральні значення (
"circle","square"), - і саме за нею TypeScript відрізняє, який тип використовується зараз.
Використання
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 |
Приклад:
type Loading = { status: "loading" };
type Success = { status: "success"; data: string };
type Error = { status: "error"; message: string };
type Response = Loading | Success | Error;Приклад (не буде дискримінованим):
type Bad = { status: string; data?: string };
type Worse = { status: string; message?: string };
type InvalidUnion = Bad | Worse; // TS не зможе звужувати за statusПриклад з switch і exhaustiveness check
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
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 - дискримінантна властивість.
Стани завантаження
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 не знає, як розрізняти члени об'єднання:
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);
}
}З дискримінатором усе стає строго типізованим і безпечним:
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і одразу знає, до якого «громадянства» (типу) він належить.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.