typescript_channels.txt (1482B)
1 ------------------------------------------------------------------------------- 2 TypeScript Channels/Signals Pattern 3 ------------------------------------------------------------------------------- 4 5 The Implementation 6 7 ```typescript 8 export type Channel<T> = { 9 subscribe(fn: (v: T) => void): () => void; 10 next(value: T): void; 11 }; 12 13 export function createChannel<T>(): Channel<T> { 14 const subs = new Set<(v: T) => void>(); 15 return { 16 subscribe(fn) { 17 subs.add(fn); 18 return () => subs.delete(fn); 19 }, 20 next(value) { 21 for (const fn of subs) fn(value); 22 }, 23 }; 24 } 25 ``` 26 27 Example 28 29 ```typescript 30 const messages = createChannel<string>(); 31 32 // producer: a websocket handler that knows nothing about the UI 33 ws.onmessage = (e) => messages.next(e.data); 34 35 // consumer: a component that knows nothing about the websocket 36 useEffect(() => messages.subscribe((m) => setLog((p) => [...p, m])), []); 37 ``` 38 39 Explanation 40 41 A channel is just a set of callbacks plus two operations: `subscribe` adds one 42 and hands back a function that removes it, and `next` calls every callback with 43 a value. That is the whole primitive — no buffering, no replay, no async. Its 44 value is decoupling: the producer only knows how to emit, the consumer only 45 knows how to listen, and neither imports the other. Unsubscribing is the one 46 thing you must not forget, which is why `subscribe` returns the cleanup 47 function directly — in React, returning it from `useEffect` is all the teardown 48 you need.