- Can someone give a simple definition of what
Recordis?
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, sinceKhas 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
Kgeneric 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!
Answer from jcalz on Stack Overflow
- Can someone give a simple definition of what
Recordis?
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, sinceKhas 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
Kgeneric 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!
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:
- If I forget a cat, I get an error.
- If I add a cat that's not allowed, I get an error.
- If I later change
CatNames, I get an error. This is especially useful becauseCatNamesis likely imported from another file, and likely used in many places.
Real-world React example.
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.
Docs
Wonderful to see that my silly little cat example is now part of the official TypeScript docs!
https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type
Videos
I am trying to create a Record type where I provide a union type of string litterals as key but rather than all the keys being required, I would like them to be optional
ie I would like baz0, baz1 and baz2 to all pass
type MyType = Record<"foo" | "bar", string>
const baz0: MyType = {
foo: "hi"
} // Property 'bar' is missing in type '{ foo: string; }' but required in type 'Record<"foo" | "bar", string>'
const baz1: MyType = {
bar: "bye"
} // Property 'foo' is missing in type '{ foo: string; }' but required in type 'Record<"foo" | "bar", string>'
const baz2: MyType = {
foo: "hi",
bar: "bye"
} // OKanyone any suggestions?