Перейти к содержимому

Discriminated Unions (tagged union)

Идиома №1 для моделирования состояний (loading/success/error, события, ADT). На интервью обязательно спросят про exhaustiveness check через never. Красный флаг — забывать дефолт-ветку, делать не-литеральный дискриминатор, использовать boolean там, где нужен ‘success’ | ‘error’.

Состояние UI как DU:

type State<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
function render<T>(s: State<T>): string {
switch (s.status) {
case 'idle': return '';
case 'loading': return '...';
case 'success': return JSON.stringify(s.data);
case 'error': return s.error.message;
default: {
// exhaustiveness check
const _never: never = s;
return _never;
}
}
}

Без exhaustiveness check добавление нового варианта молча сломает логику:

type Event =
| { type: 'click'; x: number; y: number }
| { type: 'key'; key: string };
function handle(e: Event) {
switch (e.type) {
case 'click': return `${e.x},${e.y}`;
case 'key': return e.key;
// Добавим новый вариант 'wheel' — компилятор подсветит default ниже
}
}

Хелпер assertNever и pattern matching через satisfies:

function assertNever(x: never): never {
throw new Error('unhandled: ' + JSON.stringify(x));
}
type Shape =
| { kind: 'circle'; r: number }
| { kind: 'square'; size: number };
const area = (s: Shape) => {
switch (s.kind) {
case 'circle': return Math.PI * s.r ** 2;
case 'square': return s.size ** 2;
default: return assertNever(s);
}
};

Итог: DU + literal discriminator + assertNever = безопасные конечные автоматы и события без забытых веток.