0 Tage Serie
typescript / expert
Snippet

Erschöpfende Reducer über diskriminierte Event-Ströme

`reduce<Counts>` fixiert den Accumulator-Typ, sodass der Rückgabewert in jedem Zweig strukturell gegen Counts geprüft wird statt zu `Event | Counts` zu weiten. Im switch liefert das Diskriminantenfeld `kind` jeder Case-Variante ihre spezifische Nutzlast. Der `never`-typisierte default-Zweig macht das Hinzufügen einer Variante zu einem Compile-Fehler am Reducer – nicht zu einer stillen Fehlzählung zur Laufzeit.

snippet.ts
typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
type Event =
| { kind: 'click'; x: number; y: number }
| { kind: 'key'; code: string }
| { kind: 'scroll'; delta: number };
type Counts = { click: number; key: number; scroll: number };
function tally(events: readonly Event[]): Counts {
return events.reduce<Counts>(
(acc, e) => {
switch (e.kind) {
case 'click':
return { ...acc, click: acc.click + 1 };
case 'key':
return { ...acc, key: acc.key + 1 };
case 'scroll':
return { ...acc, scroll: acc.scroll + 1 };
default: {
const _exhaustive: never = e;
return _exhaustive;
}
}
},
{ click: 0, key: 0, scroll: 0 },
);
}
Erklärung
1
type Event = | { kind: 'click'; ... } | { kind: 'key'; ... } | { kind: 'scroll'; ... };
Eine diskriminierte Union über `kind` lässt jede Variante ihre eigene Nutzlast tragen.
2
events.reduce<Counts>((acc, e) => { ... }, { click: 0, key: 0, scroll: 0 });
Der explizite Typparameter bindet den Accumulator in jeder Rückgabe an Counts.
3
switch (e.kind) { ... }
Das Switch auf die Diskriminante engt `e` in jedem Case auf eine konkrete Variante ein.
4
const _exhaustive: never = e;
Nur `never` ist `never` zuweisbar; eine unbehandelte Variante bricht hier den Build.
5
return { ...acc, click: acc.click + 1 };
Spread statt Mutation hält den Reducer pur und parallel-sicher.