Supercharge Your Code: 7 TypeScript Utility Types to Explore
TypeScript utility types are a good set of weapons for developers seeking to write more robust, flexible, and maintainable code. These powerful tools empower us to manipulate and transform existing types, tailoring them to our specific needs and making it easier to navigate complex data structures with confidence. By leveraging utility types, we can ensure type safety, reduce errors, and streamline our coding process.
In this article, we’ll learn seven essential TypeScript utility types that will enhance your coding workflow: Partial<T>, Pick<T, K>, Omit<T, K>, Readonly<T>, Required<T>, NonNullable<T>, and Parameters<T>
. Mastering these utility types will enable you to write more efficient, effective, maintainable and scalable TypeScript code, allowing you to tackle complex projects with ease and precision.
Partial<T>
The first utility type we will learn today is the Partial<T>
utility type. In TypeScript, it is used to construct a new type with all properties of the provided type T optional. This means that each property of the new type can either be of the original type T or undefined. This is very helpful in scenarios where we have many fields in a Type but only a subset of those are required in certain areas.
Example:
type Person = {
name: string;
age: number;
gender: string;
};
type PartialPerson = Partial<Person>;
const partialPerson: PartialPerson = {
name: "Alice",
age: 30
// gender property is optional in PartialPerson type
};
partialPerson.age = 18; // Valid
partialPerson.gender = "Male"; // Valid
const emptyPartialPerson: PartialPerson = {}; // Valid
In the above example, the Partial<Person>
type is created from the Person type, which makes all required properties of Person optional. This allows flexibility when working with objects of type PartialPerson as you can choose which properties to include or update. You can think of a use case where you are updating a user profile on the backend but only want to update those fields sent by the frontend. In that case, you may still want to validate the properties of a valid user but won’t need to define all properties.
Pick<T, K>
The Pick<T, K> utility type, as the name suggests, is a utility type in TypeScript used to pick a subset of properties K from another type T. This utility type allows you to create a new type by picking specific properties from an existing type.
Example:
type Car = {
make: string;
model: string;
price: number;
color: string;
};
type CarInfo = Pick<Car, 'make' | 'model' | 'price'>; const carInfo: CarInfo = { make: "Ford", model: "EcoSport", price: 902020 // color property is not included };
In the example above, the Pick type is used to selectively extract the desired properties (make, model, and price) from the Car type, resulting in a new type that only comprises the chosen properties. This enables you to work with objects that possess a curated subset of properties from the original type. In practical scenarios, you may encounter large interfaces with numerous properties, and the Pick type enables you to cherry-pick the relevant properties based on your specific needs.
Omit<T, K>
The Omit<T, K> utility type in TypeScript is one of my favorites, and I use this when I don’t have complete data with me. Hence, it is useful when we need to remove some properties K from another type T. This utility type allows you to create a new type by omitting specific properties from an existing type.
Example:
type User = {
id: number;
name: string;
email: string;
mobile: string;
isVerified: boolean;
isAdmin: boolean;
};
type PublicUser = Omit<User, 'id' | 'isAdmin' | 'isVerified'>;
const publicUser: PublicUser = {
name: "Alice",
email: "alice@example.com",
mobile: "0123456789"
// id, verified and isAdmin properties are excluded
};
In the above example, the Omit<User, 'id' | 'isAdmin' | 'isVerified'>
type is created to exclude specific properties (id, isAdmin, and isVerified) from the User type. This is helpful in cases where we don’t want to expose some private data. While working on a backend application, this helps a lot in removing admin-only fields before returning the response to the frontend. The PublicUser type now contains all properties of the User type except the excluded properties, allowing you to work with objects that have a subset of properties from the original type. So start using Omit<T, K>
whenever you feel you should not expose some secret fields to the frontend or to other parts of your application.
Readonly<T>
In a big application, we might face a situation where we end up modifying some fields which we should not. While there are multiple ways to solve this accidental mutation of properties, one way is to use the Readonly<T>
utility type in TypeScript. It is used to construct a type where all properties of the provided type T are read-only. This means that once an object of type Readonly<T>
is created, its properties cannot be reassigned.
Example:
type MutableUser = {
name: string;
email: string;
};
type ReadOnlyUser = Readonly<MutableUser>;
const readOnlyUser: ReadOnlyUser = {
name: "Bob",
email: "example@example.com"
};
// readOnlyUser.name = "Alpha"; // Error
console.log(readOnlyUser.name); // Output: Bob
In this example, the Readonly<MutableUser>
type is created to make all properties of the MutableUser type read-only in the ReadOnlyUser type. Once an object of type ReadOnlyUser is created, its properties cannot be modified or reassigned. The Readonly<T>
utility type is useful when you want to create immutable objects in TypeScript, ensuring that certain properties cannot be changed after initialization. It helps enforce immutability and can be particularly beneficial in scenarios where data integrity and consistency are essential.
Required<T>
The Required<T> utility type in TypeScript is used to construct a type where all properties of the provided type T are required. This means that all properties in the new type must be provided when creating an object of that type.
Example:
type PartialUser = {
name?: string;
email?: string;
};
type RequiredUser = Required<PartialUser>;
const requiredUser: RequiredUser = {
name: "Alice",
email: "example@example.com"
// Both name and email properties are required
};
In this example, the Required<PartialUser>
type is created to make all properties of the PartialUser type required in the RequiredUser type. When creating an object of type RequiredUser, all properties must be provided to satisfy the type definition. The Required<T> utility type is useful when you want to enforce that all properties of a type are mandatory, ensuring that certain properties cannot be left undefined when creating objects. One specific use case is when a user registers on your platform, you ask for minimal information, but when he verifies his email and mobile, confirming he is a real user, and then makes other fields required.
NonNullable<T>
The NonNullable<T>
utility type in TypeScript is used to construct a type by excluding null and undefined from the provided type T. This utility type ensures that the resulting type does not include null or undefined as possible values for its properties.
Example:
type SecondaryUser = {
name: string | null;
email: string | undefined;
};
type NonNullableUser = NonNullable<SecondaryUser>;
const nonNullableUser: NonNullableUser = {
name: "Alice",
email: "example@example.com"
// Null and undefined are excluded
};
In this example, the NonNullable<User>
type is created to exclude null and undefined from the properties of the User type, resulting in the NonNullableUser type. Objects of type NonNullableUser will not allow null or undefined as valid values for its properties. The NonNullable<T>
utility type is useful in cases where you want to make sure the object does not have any properties as null or undefined. It helps prevent potential runtime errors caused by unexpected null or undefined values in TypeScript code.
Parameters <T>
The final utility type we’ll explore is the Parameters<T> type, which enables you to extract the parameter types of a given function type T and represent them as a tuple type in TypeScript.
Example:
type AddNumberFunction = (a: number, b: number) => number;
type AddNumberFunctionParams = Parameters<AddNumberFunction>; // Result: [number, number]
const parameters: AddNumberFunctionParams = [5, 10]; // Valid
// const wrongParams: AddNumberFunctionParams = ['a', 'b']; // Invalid
In this example, the AddNumberFunction type represents a function that takes two parameters of type number and returns a number. So when we do Parameters<AddNumberFunction>
, we extract the parameter types of the AddNumberFunction type as a tuple [number, number]. This allows us to access the individual parameter types and use them accordingly. In scenarios where you want to dynamically check for types, validation, or manipulation of function parameters, use the Parameters<T>
utility type.
Conclusion
TypeScript utility types offer a wide range of benefits for developers, from creating optional properties to extracting parameter types. By understanding and utilizing these utility types, developers can write more robust, flexible, and maintainable code. Whether working on a small project or a large-scale application, incorporating TypeScript utility types into your workflow can help catch errors early, eliminate possibilities of certain bugs, improve code readability, and ensure type safety. With practice and experience, you will become proficient in using these utility types to tackle complex typing challenges and take your TypeScript skills to the next level.