TypeScript has arguably been the greatest thing that I have ever come across as a developer because of the way it revolutionized my experience as a developer. I want to help make using TypeScript even better for you by showing you how to use 7 built in utility types that will make your life much easier.
I will be using the following interface throughout the article to demonstrate the various types:
export interface Book {
name: string;
author: string;
datePublished: Date;
price: number;
numSales?: number;
seriesName?: string;
}
Omit and Pick
The Omit
type allows you to "omit" (i.e. remove) certain properties from an interface and construct a new type out of it. The first argument it takes is the interface you want to remove properties from, and the second argument is a list of parameters.
For example, if you want to make a BookProfile
type, which just has all the properties of the book needed to identify itself, you would use the Omit
as follows:
type BookInfo = Omit<Book, "price" | "numSales">;
This type would be equivalent to our Book
interface except it wouldn't have the price
or numSales
properties. You can also create it with the interface keyword as follows:
interface IBookInfo extends Omit<Book, "price" | "numSales"> {}
The IBookInfo
interface would work the same way the BookInfo
type would, but I personally prefer using the type version due to its cleaner implementation.
The Pick
type has quite literally the opposite function of the Omit
type; the Pick
type allows you to "pick" certain properties of the interface that is passed in and construct a new type out of that.
If we were to make the BookInfo
type using the Pick
type, here is how it would look:
type BookInfo = Pick<
Book,
"name" | "author" | "datePublished" | "seriesName"
>;
Because these 2 types are so similar, an important question arises. "In which cases should I use Omit
or Pick
?" I think it actually comes down to personal preference.
If you were to add new properties to the Book
type, it would automatically be added to the BookInfo
type using Omit
and cut off from the type using Pick
, so it really depends on the scenario.
Generally, if you have a bunch of properties you need to remove, or if you need to remove more properties than you need to use, I'd use the Pick
type; otherwise, I'd use Omit
.
Partial and Required
Just like Omit
and Pick
, Partial
, and Required
are also related in terms of functionality.
In our Book
interface, there are 4 required properties and 2 optional properties.
The Partial
type will make all the properties on the interface passed in optional; this means that all 6 properties on the Book
interface would become required.
type BookPartial = Partial<Book>;
The Required
type does the exact opposite of what Partial
does; it makes all the properties on the interface passed in required. This would make all 6 properties on Book
required.
type BookRequired = Required<Book>;
These types are usually extended to include some exceptions. For example, we could make the name
property required on the BookPartial
type by extending it as follows:
type BookPartial = Partial<Book> & { name: string };
Record
The Record
type allows you to create an object where you can define the key and the type. Lets say we had the following object below:
const myObj = {
a: 1,
b: 2,
};
This object follows a strict pattern where every key is a string and every value is a number. We can type this object using the Record
type as follows:
const myObj: Record<string, number> = {
a: 1,
b: 2,
};
All the Record
type does is say what the types are for the key-value pair of an object. If you want to represent a shelf with different books (with the Book
interface), you can now easily do that with Record
as follows:
const shelf: Record<number, Book> = {
0: {
name: "Into Thin Air",
author: "Jon Krakauer",
datePublished: new Date(1997, 1),
price: 14,
},
1: {
name: "1984",
author: "George Orwell",
datePublished: new Date(1949, 6),
price: 12,
},
};
Parameters and ReturnType
The Parameters
type creates an array type based on a function's arguments. For example, let's say we had the following function:
function createPost(caption: string, image?: string) {
return { caption, image };
}
If you wanted to create a type that contained all the arguments of createPost
, you would do it as follows:
type CreatePostInput = Parameters<typeof createPost>;
Here is how the type would be used in action:
const post: CreatePostInput = ["Nice", "https://bit.ly/3OOiIaC"];
createPost(...post);
The reason why you have to use the spread operator with the post
array is because the elements of the array correspond to the various arguments of the function; the array itself isn't a function argument.
The ReturnType
creates a type based on what a function returns. The syntax is very similar to that of Parameters
:
type CreatePostReturn = ReturnType<typeof createPost>;
This is equivalent to:
type CreatePostReturn = {
caption: string;
image: string | undefined;
};
If a function returns a promise, you can wrap the ReturnType
around the Awaited
utility type, which will effectively do the same thing the await
keyword does. It will allow you to have access to the object's (that is wrapped in the Promise) properties again.
type CreatePostReturn = Awaited<ReturnType<typeof createPost>>;
Next Steps
Now that you have learned just how powerful some of these types are, I highly reccomend refactoring some of your old code to use these types or even implement these types in a new app your building!
Thank you for reading!
Signing off ๐