Advanced TypeScript Utility Types in Detail

September 27, 2024 (1mo ago)

Advanced TypeScript Utility Types in Detail

TypeScript provides a set of utility types that help transform or manipulate types in a more flexible and powerful way. These types are useful for building more dynamic and reusable codebases. These utility types are built-in and can be used to create new types based on existing ones. In this article, we'll explore some of the advanced TypeScript utility types and how they can be used to improve your code.

1. Partial<Type>

The Partial utility type makes all properties of the given type T optional. It is useful when you want to work with objects where only some properties may be provided, or when you're working with optional values in forms or updates.

interface User {
    id: number;
    name: string;
    email: string;
}
 
// All properties are now optional
type PartialUser = Partial<User>;
 
function updateUser(id: number, user: PartialUser) {
    // Update user properties
}
 
updateUser(id: 1, { name: "Aasim" });

2. Required<Type>

The Required utility type does the opposite of Partial. It makes all properties of the given type T required, ensuring that all properties must be present.

interface User {
    id: number;
    name?: string; // Optional property
    email?: string; // Optional property
}
 
// All properties are now required
type RequiredUser = Required<User>;
 
const user: RequiredUser = {
    id: 1,
    name: "Aasim",
    email: "contact@bhataasim.com",
};

3. Readonly<Type>

The Readonly utility type makes all properties of the given type T readonly, meaning they cannot be modified once they are created. It is useful when you want to prevent accidental changes to objects.

interface User {
    id: number;
    name: string;
    email: string;
}
 
// All properties are now readonly
type ReadonlyUser = Readonly<User>;
 
const user: ReadonlyUser = {
    id: 1,
    name: "Aasim",
    email: "contact@bhataasim.com",
};
 
// Error: Cannot assign to 'name' because it is a read-only property
user.name = "Bhat Aasim";

4. Pick<Type, Keys>

The Pick utility type allows you to create a new type by selecting only a subset of properties from the given type T. It is useful when you want to create a new type with only specific properties.

interface User {
    id: number;
    name: string;
    email: string;
    age: number;
}
 
// Select only 'id' and 'name' properties
type UserBasicInfo = Pick<User, "id" | "name">;
 
const user: UserBasicInfo = {
    id: 1,
    name: "Aasim",
};

5. Omit<Type, Keys>

The Omit utility type is the opposite of Pick. It allows you to create a new type by excluding a subset of properties from the given type T. It is useful when you want to create a new type without specific properties.

interface User {
    id: number;
    name: string;
    email: string;
    age: number;
}
 
// Exclude 'id' and 'email' properties
type UserPersonalInfo = Omit<User, "id" | "email">;
 
const user: UserPersonalInfo = {
    name: "Aasim",
    age: 21,
};

6. Exclude<Type, ExcludedUnion>

The Exclude utility type removes all types from Type that are assignable to ExcludedUnion. It is useful when you want to exclude certain types from a union.

type Status = "active" | "inactive" | "pending";
 
type ActiveStatus = Exclude<Status, "pending">;
 
const status: ActiveStatus = "active";
 
// Error: Type '"pending"' is not assignable to type 'ActiveStatus'.
const status: ActiveStatus = "pending";

7. Extract<Type, Union>

Extract does the opposite of Exclude. It constructs a type by extracting all types from Type that are assignable to Union. It is useful when you want to extract certain types from a union.

type Status = "active" | "inactive" | "pending";
 
type ActiveStatus = Extract<Status, 'active' | 'inactive'>;
 
const status: ActiveStatus = "active";
 
// Error: Type '"pending"' is not assignable to type 'ActiveStatus'.
const status: ActiveStatus = "pending";

8. Record<Keys, Type>

The Record utility type constructs an object type whose property keys are Keys and whose property values are Type. It is useful when you want to create an object type with specific keys and values. It is commonly used to create dictionaries or maps in TypeScript.

// example 1
type Roles = "admin" | "user" | "guest";
 
type UserRoles = Record<Roles, boolean>;
 
const roles: UserRoles = {
    admin: true,
    user: true,
    guest: false,
};
 
// example 2
type PageInfo = {
    title: string,
    url: string,
};
 
type Page = 'home' | 'about' | 'contact';
 
const pages: Record<Page, PageInfo> = {
    home: { title: 'Home', url: '/' },
    about: { title: 'About', url: '/about' },
    contact: { title: 'Contact', url: '/contact' },
};

9. NonNullable<Type>

The NonNullable utility type removes null and undefined from the given type T. It is useful when you want to ensure that a value is not null or undefined.

type User = {
    id: number;
    name: string | null;
};
 
// Remove null and undefined
type NonNullableUser = NonNullable<User>;
 
const user1: NonNullableUser = {
    id: 1,
    name: "Aasim",
};
 
const user2: NonNullableUser = {
    id: 2,
    name: null, // Error: Type 'null' is not assignable to type 'string'.
};

10. ReturnType<Type>

The ReturnType utility type extracts the return type of a function type. It is useful when you want to get the return type of a function dynamically.

function getUser() {
    return { id: 1, name: "Aasim" };
}
 
type User = ReturnType<typeof getUser>;
 
const user: User = {
    id: 1,
    name: "Aasim",
};

11. Parameters<Type>

The Parameters utility type extracts the parameter types of a function type as a tuple. It is useful when you want to get the parameter types of a function dynamically.

function loginUser(username: string, password: string) {
    // Login logic
}
 
type LoginParams = Parameters<typeof loginUser>;
 
function authenticateUser(...params: LoginParams) {
    // Authenticate user
}
 
authenticateUser("admin", "password");
 
authenticateUser("user", 123); // Error: Argument of type 'number' is not assignable to parameter of type 'string'.

Conclusion

These are some of the advanced TypeScript utility types that can help you write more robust and flexible code. By leveraging these utility types, you can create more dynamic and reusable types in your TypeScript projects.

Checkout the Official TypeScript Documentation for more utility types: TypeScript Utility Types

I hope you found this article helpful. If you have any questions or feedback, feel free to reach out. Happy coding! 🚀