DEV Community

Cover image for 11 Awesome TypeScript Utility Types You Should Know
Kai
Kai

Posted on • Edited on • Originally published at kais.blog

11 Awesome TypeScript Utility Types You Should Know

This post was originally published at kais.blog.

Let's move your learning forward together! Follow me on Twitter for your daily dose of developer tips. Thanks for reading my content!


My last two posts have already shown you 14 Awesome JavaScript Array Tips You Should Know About and 10 Awesome JavaScript String Tips You Might Not Know About. However, I usually write code in TypeScript. There's also much to learn. So today, I'd like to show you 11 awesome TypeScript utility types. With these, constructing new types becomes a breeze.

You don't have to do anything special. All utility types are globally available by default.

Pick<Type, Keys>

With Pick you can pick a set of Keys from the given Type. The example shows a signup function taking a user as first parameter. The parameter type is constructed by picking the email and password property from the User type. This way, you won't need to pass an id for signing up a new user.

interface User {
  id: string;
  email: string;
  password: string;
}

function signup(user: Pick<User, "email" | "password">): User {
  //
}

signup({
  email: "kai@example.com",
  password: "secret",
});
Enter fullscreen mode Exit fullscreen mode

Omit<Type, Keys>

In contrast to Pick, you can use Omit to remove a set of Keys from your Type. The example is similar to the previous. In this case, the signup function's parameter user has all properties from the User type minus the id property.

interface User {
  id: string;
  email: string;
  password: string;
}

function signup(user: Omit<User, "id">): User {
  //
}

signup({
  email: "kai@example.com",
  password: "secret",
});
Enter fullscreen mode Exit fullscreen mode

Readonly<Type>

Sometimes you want to prevent that an object's properties are reassigned. This is doable with the Readonly utility type. The constructed type will have all properties set to read-only. Thus, you can't reassign any property of that type. In the following example, we'll create a new type by using Readonly and the User type. We can't reassign the password property here, because it's read-only now.

interface User {
  id: string;
  email: string;
  password: string;
}

const user: Readonly<User> = {
  id: "d70989c8-c135-4825-a18c-a62ddf9ae1d5",
  email: "kai@example.com",
  password: "secret",
};

user.password = "correcthorsebatterystaple";

// ERROR: Cannot assign to 'password' because it is a read-only property.
Enter fullscreen mode Exit fullscreen mode

Partial<Type>

Using Partial you can construct a type with all properties from Type set to optional. For example, the updateUser function allows you to update a User. The second parameter expects the fields to update. You can use Partial with the User type here, so that fields is any combination of fields from the User type.

interface User {
  id: string;
  email: string;
  password: string;
}

function updateUser(user: User, fields: Partial<User>): User {
  //
}

const user: User = {
  id: "d70989c8-c135-4825-a18c-a62ddf9ae1d5",
  email: "kai@example.com",
  password: "secret",
};

updateUser(user, { password: "correcthorsebatterystaple" });
Enter fullscreen mode Exit fullscreen mode

Required<Type>

Required is the opposite of Partial. You can use it to construct a type with all properties from Type set to required. The following example has a User type with an optional avatar property. However, our variable userWithAvatar requires all properties to be present. Thus, an error occurs.

interface User {
  id: string;
  email: string;
  password: string;
  avatar?: string;
}

const userWithAvatar: Required<User> = {
  id: "d70989c8-c135-4825-a18c-a62ddf9ae1d5",
  email: "kai@example.com",
  password: "secret",
};

// ERROR: Property 'avatar' is missing in type '{ id: string; email: string; password: string; }' but required in type 'Required<User>'.
Enter fullscreen mode Exit fullscreen mode

Record<Keys, Type>

With the Record utility type, you can easily construct a new type with Keys as keys and Type as values. In this example, each User has a role. We want to describe an object that groups userA and userB by their respective role. Using Record, we can tell the TypeScript compiler that the object has a strings as keys and an array of Users as values. Besides, to be more explicit, we could have used User["role"] instead of string for the keys.

interface User {
  id: string;
  email: string;
  password: string;
  role: string;
}

const userA: User = {
  id: "d70989c8-c135-4825-a18c-a62ddf9ae1d5",
  email: "kai@example.com",
  password: "secret",
  role: "administrator",
};

const userB: User = {
  id: "c0e26c7e-9787-4d56-81b4-4440759e251c",
  email: "katharina@example.com",
  password: "correcthorsebatterystaple",
  role: "moderator",
};

const usersGroupedByRole: Record<string, User[]> = {
  administrator: [userA],
  moderator: [userB],
};
Enter fullscreen mode Exit fullscreen mode

Parameters<Type>

Use Parameters to extract a function's parameters. This will construct a tuple type with the parameters. Let's say you'd like to initialize a variable that holds parameters for a signup function. With the help of Parameters you can extract the signup function's parameters and create a tuple type. Then, you can use the parameters whenever you want.

interface User {
  id: string;
  email: string;
  password: string;
}

function signup(email: string, password: string): User {
  //
}

type SignupParameters = Parameters<typeof signup>;
//                    = [email: string, password: string]

const parameters: SignupParameters = ["kai@example.com", "secret"];

signup(...parameters);
Enter fullscreen mode Exit fullscreen mode

ReturnType<Type>

The utility type ReturnType helps by extracting the return type of a function. Look at the following example. We want our ValidationResult type to be constructed by looking at the return type of the validate function. Here, it's pretty simple. You could have used boolean directly instead. However, sometimes it's nice to be able to extract a function's return type.

interface User {
  id: string;
  email: string;
  password: string;
}

function validate(user: User): boolean {
  //
}

type ValidationResult = ReturnType<typeof validate>;
//                    = boolean
Enter fullscreen mode Exit fullscreen mode

Extract<Type, Union>

Sometimes, you'd like to extract types from an union of types. For that, you can use the Extract utility type. Every union member from Type that is assignable to the Union is kept. In the following examples, we'll have unions of strings. There, we extract a part of it for our types TypeA and TypeB. For TypeC we extract each union member that is assignable to Function.

type TypeA = Extract<"apple" | "banana" | "cherry", "apple">;
//         = "apple"

type TypeB = Extract<"apple" | "banana" | "cherry", "apple" | "banana">;
//         = "apple" | "banana"

type TypeC = Extract<string | (() => string), Function>;
//         = () => string
Enter fullscreen mode Exit fullscreen mode

Exclude<Type, ExcludedUnion>

The Exclude utility type is the opposite of the Extract utility type. This time, it removes all union members from Type that are assignable to the ExcludedUnion. Similar to the previous examples, we have unions of strings here. In contrast to last time, we are removing union members instead of keeping them.

type TypeA = Exclude<"apple" | "banana" | "cherry", "apple">;
//         = "banana" | "cherry"

type TypeB = Exclude<"apple" | "banana" | "cherry", "apple" | "banana">;
//         = "cherry"

type TypeC = Exclude<string | (() => string), Function>;
//         = string
Enter fullscreen mode Exit fullscreen mode

NonNullable<Type>

NonNullable works similar to the Exclude utility type. It excludes null and undefined from the given Type. Similar to the previous two examples, we construct the new types TypeA and TypeB by removing union members from a given Type. Here, null and/or undefined are removed.

type TypeA = NonNullable<"apple" | null>;
//         = "apple"

type TypeB = NonNullable<"apple" | null | undefined>;
//         = "apple"
Enter fullscreen mode Exit fullscreen mode

Conclusion

This was a quick overview of some of the most useful utility types. They can be used for a wide range of things. I use them in almost every project. However, there are more utility types. Check out the official documentation to learn more! Besides, you can find even more such types. For example, the type-fest package adds many essential types to your project.

Thanks a lot for reading this post. Please consider sharing it with your friends and colleagues. See you soon!


Let's move your learning forward together! Follow me on Twitter for your daily dose of developer tips. Thanks for reading my content!

This post was originally published at kais.blog.

Top comments (6)

Collapse
 
hoxtygen profile image
Idowu Wasiu

Under Record, you wrote
With the Record utility type, you can easily c.
I don't understand that statement. Can you please clarify.

Collapse
 
kais_blog profile image
Kai

Oh, sorry, that's a typo there. I've fixed the sentence. Thanks!

Collapse
 
vritb profile image
Volker Reichel

This article shows nice and useful built-in types. Since which version are they available?

Collapse
 
kais_blog profile image
Kai

Thanks! Partial, Readonly, Record, and Pick are available since at least version 2.1. For the others, I'm not sure. However, I can confirm they are available in 4.x and should be available even in multiple previous versions.

Collapse
 
vritb profile image
Volker Reichel

Thank you for your quick response. Wish you a merry christmas.

Thread Thread
 
kais_blog profile image
Kai

Thanks, Merry Christmas to you too!