TypeScript: Everyday Types - discussion (2022-06-18)
When you're typing UI state and route documents, what do you reach for most: unions, generics, discriminated records, something else?
Do you model posture keys as string unions, or as objects with discriminants? Where do you draw the boundary between wide inputs and narrow stable surfaces in a React app? And do you use runtime signals/evidence to back up types when the actual behavior can drift?
Comments (10)
Back to latestString unions for posture keys, discriminated unions for intent. Boring and readable.
I like wide at the boundary, narrow in the document.
Parse/normalize once, store the derived model, and then components don't do ad hoc narrowing.
This is basically the through-line in TypeScript and Types and API Reference.
A tiny example of posture as string unions that stays ergonomic:
ts
type CacheLane = 'bypass' | 'stale-ok' | 'strict';
type NavIntent =
| { type: 'open'; target: 'inspector'; id: string }
| { type: 'redirect'; to: '/login'; reason: string };
type RouteDoc = { cacheLane: CacheLane; intent: NavIntent; signals: { contract: string } };
Then you can render a signal like cacheLane + intent.type and the runtime story matches the type story.
Counterpoint: a lot of people use types as a substitute for ownership.
You can have perfect types and still have a doc full of keys nobody owns. The discipline is asking who owns this surface.
Yes. We've had typed junk drawers. The signals and ownership rules did more than TS wizardry.
Exactly. Types are necessary, not sufficient.
I mostly reach for plain objects because it's easier to debug. Unions are great until you end up with 12 variants nobody remembers.
We had success with a hybrid: route docs are strict and narrow; internal component state can be looser during prototyping.
Then we graduate keys into the doc once they're stable contracts.
I like storing derived shapes in the doc even when TS could derive them with inference.
It keeps runtime work bounded and makes signals honest (the thing you render is the thing you stored).
Signals are the bridge. Types tell you what should be true; signals tell you what is true right now on this route.
Practical: avoid any at the boundary. If you must, confine it to one file and normalize immediately.
We wrote this rule down after reading TypeScript Boundaries (Deep Dive #9).
Type your signals too. Otherwise signals rot and you lose the point of rendering them.