11 Awesome TypeScript Utility Types You Should Know

published

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: "[email protected]",
  password: "secret",
});

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: "[email protected]",
  password: "secret",
});

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: "[email protected]",
  password: "secret",
};

user.password = "correcthorsebatterystaple";

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

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: "[email protected]",
  password: "secret",
};

updateUser(user, { password: "correcthorsebatterystaple" });

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: "[email protected]",
  password: "secret",
};

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

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: "[email protected]",
  password: "secret",
  role: "administrator",
};

const userB: User = {
  id: "c0e26c7e-9787-4d56-81b4-4440759e251c",
  email: "[email protected]",
  password: "correcthorsebatterystaple",
  role: "moderator",
};

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

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 = ["[email protected]", "secret"];

signup(...parameters);

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

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

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

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"

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!


You May Also Be Interested in the Following Posts