I found that pattern useful when you don't know what the key will be. I have an iOS app that tracks tips, when you add a tip, it's stored in an object like this:
type Tips = {
[tipGuid: string]: TipObject
}
which can be rewritten using Record as
type Tips = Record<string, TipObject>
That pattern ins't very useful when creating object with known keys but for data structures where the key is either not known or generated it a godsend.
It’s not true that Record will result in a type where any key is valid. If you pass in a primitive like string, then of course any string will be valid. That’s not Record’s fault; what you’re doing is essentially creating an index signature [1]. If you pass a more restrictive type in as the key, it works as expected:
type Tips = Record<“foo”, TipObject>;
const tips: Tips = {}; // error, needs key “foo”
tips["foo"]; // fine
tips["bar"]; // error, no key “bar” in tips
It’s worth mentioning that this isn’t just an issue with objects. For example, by default, the index type on arrays is unsafe:
const arr: number[] = [];
const first: number = arr[0]; // actually undefined, but typescript allows it
If you do need an index type and want to account for undefined keys, the idiomatic way is the noUncheckedIndexAccess compiler flag [2], which will automatically make any index property access a union with undefined.
IMO non constant (as defined by TypeScript) arrays should’ve been automatically assigned a union type with `undefined`, which can also be a fix for Records too:
While this is probably alright for some data, I'd definitely recommend using something like a Map instead (especially if the object mutates) for things you have control over (ie it's not describing an endpoint or something similar).
type Tips = { [tipGuid: string]: TipObject }
which can be rewritten using Record as
type Tips = Record<string, TipObject>
That pattern ins't very useful when creating object with known keys but for data structures where the key is either not known or generated it a godsend.