5 ms·
The third example describes something useful in record types, but goes about it in what seems an odd way, and ends up suboptimal as a result. I'd instead use an
by throwanem 5y ago
The third example describes something useful in record types, but goes about it in what seems an odd way, and ends up suboptimal as a result. I'd instead use an object type like this:
type Human = {
name: string;
age: number;
}
which also enforces value types in the compiler, rather than requiring runtime guards.
- 3np 5y agoI think the example type is useful for unvalidated input data e.g. from an API call. Or an update function for a DB abstraction. Internally in the backend, you’d still use the type you just posited.
- throwanem 5y agoEh. A good ORM provides type definitions from model definitions, which is one way I've found ORMs more useful in TS than JS, and I'd more likely use a runtype or a decoder to both validate and type inbound data than roll my own interface for it. On review of documentation, I was actually pretty off base in grandparent comment. The real use case for Record appears to be when you need a map type whose keys are both explicitly enumerated and defined elsewhere, ie in a union, enum, or otherwise unrelated object type. Rather than duplicating the keys, you can use Record<someUnion, V> or Record<keyof typeof someEnum, V> and only have to make one change to update both. For the "arbitrary keys, known value types" case I mentioned earlier, an object type with an index signature works fine and may be more legible.
- chana_masala 5y ago> an object type with an index signature works fine and may be more legible. If I understand you correctly, that's exactly what a Record is underneath
- throwanem 5y agoMore or less. There are extra steps involved with a Record, but they work the same iirc.
- _fat_santa 5y agoI 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.
- spoiler 5y agoWhile 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).
- phailhaus 5y ago`Record` is a dangerous type, and I would recommend against it. The problem is that it assumes any key is valid and will return a value type. Example: type Tips = Record<string, TipObject> const tips: Tips = {} tips["hello"] // TipObject, but you actually get undefined It's better to define a Dictionary type like so: type Dictionary<K extends string | number | Symbol, V> = Partial<Record<K, V>> Then to use: type Tips = Dictionary<string, TipObject> const tips: Tips = {} tips["hello"] // TipObject | undefined That way, you're always forced to check for existence, and you never accidentally attempt to access properties on `undefined`.
- throwaway284534 5y agoYou’re right that this is really easy to mess up, especially when defining an array index e.g. const todos: string[] = [“walk dog”]; todos[123].toUpperCase() // error! 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: type Tips = Record<string, TipObject | undefined>
- jakelazaroff 5y ago
- mikeryan 5y agoI think the example is a bit contrived with a preset union. Where it’s really valuable is when you’re extracting a union from another source (like via keyof) and want to keep the two objects in sync without having to modify the keys in two places.
- wdfx 5y agoThe type itself is fine but how it is checked and used is not. The following would be better, and in this case you will also have a Human type inside the forEach callback: // ... other code type Human = { name: string; age: number } const isHuman = (obj: unknown): obj is Human => obj && typeof obj === 'object' && 'name' in obj && 'age' in obj; // you can complete the gaps here and also check the property types someArray.filter(isHuman).forEach((h) => { // h has type Human now console.log(h.age); })
- jsf01 5y agoThe type guard may be useful when transferring that array server->client or vice versa, or for an array from any other untrusted source, but I don’t think that was the purpose of the demo in the article. When the array in question is coming from the same code base that kind of runtime type check is redundant.
- throwanem 5y agoThis is reasonable for validating an untrusted object, sure, but I don't recall that constraint being expressed as part of what the original post was addressing.
- OJFord 5y agoWhich is exactly what was used in the code exemplifying something else in the second example - so that confused me (never having written any TS) too.
- throwanem 5y agoYeah, typically you want a record type for something like a mapping over potentially arbitrary keys (via an index type) to values of known type, and an object type (which can have optional keys) when you do know exactly what shape you expect and want to enforce it.