What does Record<K, T>
mean in Typescript?
Typescript 2.1 introduced the Record
type, describing it in an example:
// For every properties K of type T, transform it to U function mapObject<K extends string, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U>
see Typescript 2.1
And the Advanced Types page mentions Record
under the Mapped Types heading alongside Readonly
, Partial
, and Pick
, in what appears to be its definition:
type Record<K extends string, T> = { [P in K]: T; }
Readonly, Partial and Pick are homomorphic whereas Record is not. One clue that Record is not homomorphic is that it doesn’t take an input type to copy properties from:
type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>
And that's it. Besides the above quotes, there is no other mention of Record
on typescriptlang.org.
Can someone give a simple definition of what Record
is?
Is Record<K,T>
merely a way of saying "all properties on this object will have type T
"? Probably not all properties, since K
has some purpose...
Does the K
generic forbid additional keys on the object that are not K
, or does it allow them and just indicate that their properties are not transformed to T
?
With the given example:
type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>
Is it exactly the same as this?:
type ThreeStringProps = {prop1: string, prop2: string, prop3: string}
A record type is a composite data type that consists of one or more identifiers and their corresponding data types. You can create user-defined record types by using the TYPE IS RECORD statement within a package or by using the CREATE TYPE (Object) statement. Dot notation is used to reference fields in a record.
TypeScript adds a typeof operator you can use in a type context to refer to the type of a variable or property: let s = "hello"; let n : typeof s ; let n: string. This isn't very useful for basic types, but combined with other type operators, you can use typeof to conveniently express many patterns.
❮ Previous Next ❯ TypeScript comes with a large number of types that can help with some common type manipulation, usually referred to as utility types.
Record<string, never> doesn't actually represent an empty object, but rather an object where every property is type never . As never is the bottom type and models functions that throw exceptions, it's assignable to everything.
- Can someone give a simple definition of what
Record
is?
A Record<K, T>
is an object type whose property keys are K
and whose property values are T
. That is, keyof Record<K, T>
is equivalent to K
, and Record<K, T>[K]
is (basically) equivalent to T
.
- Is
Record<K,T>
merely a way of saying "all properties on this object will have typeT
"? Probably not all objects, sinceK
has some purpose...
As you note, K
has a purpose... to limit the property keys to particular values. If you want to accept all possible string-valued keys, you could do something like Record<string, T>
, but the idiomatic way of doing that is to use an index signature like { [k: string]: T }
.
- Does the
K
generic forbid additional keys on the object that are notK
, or does it allow them and just indicate that their properties are not transformed toT
?
It doesn't exactly "forbid" additional keys: after all, a value is generally allowed to have properties not explicitly mentioned in its type... but it wouldn't recognize that such properties exist:
declare const x: Record<"a", string>; x.b; // error, Property 'b' does not exist on type 'Record<"a", string>'
and it would treat them as excess properties which are sometimes rejected:
declare function acceptR(x: Record<"a", string>): void; acceptR({a: "hey", b: "you"}); // error, Object literal may only specify known properties
and sometimes accepted:
const y = {a: "hey", b: "you"}; acceptR(y); // okay
With the given example:
type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>
Is it exactly the same as this?:
type ThreeStringProps = {prop1: string, prop2: string, prop3: string}
Yes!
Hope that helps. Good luck!
A Record lets you create a new type from a Union. The values in the Union are used as attributes of the new type.
For example, say I have a Union like this:
type CatNames = "miffy" | "boris" | "mordred";
Now I want to create an object that contains information about all the cats, I can create a new type using the values in the CatNames
union as keys.
type CatList = Record<CatNames, {age: number}>
If I want to satisfy this CatList
, I must create an object like this:
const cats: CatList = { miffy: { age:99 }, boris: { age:16 }, mordred: { age:600 } }
You get very strong type safety:
CatNames
, I get an error. This is especially useful because CatNames
is likely imported from another file, and likely used in many places.I used this recently to create a Status
component. The component would receive a status
prop, and then render an icon. I've simplified the code quite a lot here for illustrative purposes
I had a union like this:
type Statuses = "failed" | "complete";
I used this to create an object like this:
const icons: Record< Statuses, { iconType: IconTypes; iconColor: IconColors } > = { failed: { iconType: "warning", iconColor: "red" }, complete: { iconType: "check", iconColor: "green" };
I could then render by destructuring an element from the object into props, like so:
const Status = ({status}) => <Icon {...icons[status]} />
If the Statuses
union is later extended or changed, I know my Status component will fail to compile and I'll get an error that I can fix immediately. This allows me to add additional error states to the app.
Note that the actual app had dozens of error states that were referenced in multiple places, so this type safety was extremely useful.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With