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.