Record<Keys, Type> Utility Types in TypeScript
The Record type is a type that can be used to combine two types.
The Record type is a utility type that can be used to combine two types. It allows you to create an object type whose property keys are of a specific type and whose property values are of another specific type.
Syntax
Let's have a look at the Syntax of record type.
Record<Keys, Type>;
- Keys: This is the type of object's keys that the record type generates for us.
- Type: This is the type of the object's value generated by the record type.
Example
Let's take an example to understand how the Record type works. Let us suppose we
are creating a blogging application and we have type roles which is a union type
of three types of roles for the users of the application, the author
, editor
and
a researcher
.
type Roles = "author" | "editor" | "researcher";
Each of the users in the application would also need to have a name
an email
and
age
properties linked to their account. So we can create an interface for a
User
object as well.
interface User {
name: string;
email: string;
age: number;
}
Now moving on to articles in our blog, we would want each article to be an object which might have the following properties.
const article = {
title: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
content: "Duis est urna, eleifend at malesuada id, suscipit eu",
// Contributors can be type generated from Roles type and User interface
contributors: {
author: { name: "John", email: "john@email.com", age: 32 },
editor: { name: "Frank", email: "frank@email.com", age: 36 },
researcher: { name: "Mark", email: "mark@email.com", age: 36 },
},
};
So our aim here is to type the article object strictly. If we look closely,
contributors can be type generated from Roles
type and User interface. If we
look at the contributors
object we can infer that each property key of the
contributors object is each value from the Roles type, which is a union type and
value for each of the properties of the contributors
that is each of the users
object complies with our User
interface. So we can say that while the Roles
type
is the key and the User
interface is the value of the contributors
object.
This
is a perfect case for using the Record<Keys, Type>
utility type. So let's
create
another interface called Article and start strictly typing the article object.
interface Article {
title: string;
content: string;
contributors: Record<Roles, User>;
}
The contributors property is of type Record<Roles, User>
, which means that it
is
an object whose keys are of type Roles and whose values are of type User
. The
Record<Keys, Type>
utility type will assign the exact shape to
our contributors
object as declared inside our article object. Now we can assign the Article
interface to our article object and this is how the whole code would look like.
interface User {
name: string;
email: string;
age: number;
}
type Roles = "author" | "editor" | "researcher";
interface Article {
title: string;
content: string;
contributors: Record<Roles, User>;
}
const article: Article = {
title: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
content: "Duis est urna, eleifend at malesuada id, suscipit eu",
contributors: {
author: { name: "John", email: "john@email.com", age: 32 },
editor: { name: "Frank", email: "frank@email.com", age: 36 },
researcher: { name: "Mark", email: "mark@email.com", age: 36 },
},
};
Time To Transition From JavaScript To TypeScript
Level Up Your TypeScript And Object Oriented Programming Skills. The only complete TypeScript course on the marketplace you building TypeScript apps like a PRO.
SEE COURSE DETAILSWe used a union type to define Roles
, but we could have used an enum as well
to
produce the same results. Here is an example, we can comment out the Roles
type
and declare and enum called Roles
to produce the same results.
// type Roles = "author" | "editor" | 'researcher';
enum Roles {
author = "author",
editor = "editor",
researcher = "researcher",
}
Using Mapped Types With Record Utility Type
Another scenario you'll often come across is where you would want to use mapped types along with the Record utility type. Here is an example, let us assume that in our application we have a frontend where the user has an interface to change the title and the content of the article and we need to extract those as strings before we parse them and send them to the API to be saved as new values.
In such scenario we can create a new type called ArticleData
using
the Record
type and mapped types. This is how we can do it.
type ArticleData = Record<Article, string>;
If we were to infer the ArticleData
type we would see that even the
contributed
is a part of it and this is how it looks like.
type ArticleData = {
title: string;
content: string;
contributors: string;
};
We might now want the contributors to be a part of the new type as we just need
the data that is the title as well as the content. In this case, we can use
another TypeScript utility type called the Omit<Type, Keys>
type.
The Omit<Type,Keys>
utility type let's us omit or remove any of the properties if we want to
from an object type or an interface and in this case it will be the contributors
object. So we can further refine our ArticleData
type like so:
type ArticleData = Record<keyof Omit<Article, "contributors">, string>;
Now we have ArticleData
type that only contains the properties that we need.
type ArticleData = {
title: string;
content: string;
};
Now this might seem useless at this point and creating another type might seem
to be a more convenient way of dealing with the problem, but when the interfaces
that you need to inherit from become very long Record<Keys, Types>
proves to be
very useful.
What Can You Do Next 🙏😊
If you liked the article, consider subscribing to Cloudaffle, my YouTube Channel, where I keep posting in-depth tutorials and all edutainment stuff for software developers.