Skip to main content

Що таке discriminant property?

Що таке 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 і одразу знає, до якого «громадянства» (типу) він належить.

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

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

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