Utility Types in TypeScript

Utility Types in TypeScript

This article explains utility types in TypeScript.

YouTube Video

Utility Types in TypeScript

TypeScript utility types are convenient tools for creating new types based on existing types. This allows for more flexible type definitions and increases code reusability. Here, we will explain commonly used utility types in detail and discuss how to use each one with sample code.

Partial<T>

Partial<T> makes all properties of an object type optional (allowing undefined). It is useful when you want to use only some of the properties that the original type has.

 1interface User {
 2  id: number;
 3  name: string;
 4  age: number;
 5}
 6
 7function updateUser(user: Partial<User>) {
 8  console.log(user);
 9}
10
11updateUser({ name: "Alice" }); // Updates only 'name'
  • In the example above, using Partial<User> makes all properties of the User type optional. Therefore, in the updateUser function, you can pass only a subset of the properties.

Required<T>

Required<T> makes all properties, including optional ones, required. It is used when you want to convert properties that are optional to be required.

 1interface User {
 2  id: number;
 3  name?: string;
 4  age?: number;
 5}
 6
 7function createUser(user: Required<User>) {
 8  console.log(user);
 9}
10
11// createUser({ id: 1 }); // Error: 'name' and 'age' are required
12createUser({ id: 1, name: "Alice", age: 25 });
  • By using Required<User>, properties such as name and age are treated as required.

Readonly<T>

Readonly<T> makes all properties of an object read-only. This prevents the values of the object from being changed.

 1interface User {
 2  id: number;
 3  name: string;
 4}
 5
 6const user: Readonly<User> = {
 7  id: 1,
 8  name: "Alice"
 9};
10
11// user.id = 2; // Error: 'id' is read-only
12console.log(user);
  • By using Readonly<T>, you can protect an object's properties from being altered. It is effective when you want to prevent data from being accidentally modified during development.

Record<K, T>

Record<K, T> creates a map type with specified key and value types. K is the type for the keys (such as string or number), and T is the type for the values.

 1type Roles = "admin" | "user" | "guest";
 2interface ReadWritePermissions {
 3  read: boolean;
 4  write: boolean;
 5}
 6
 7const rolePermissions: Record<Roles, ReadWritePermissions> = {
 8  admin: { read: true, write: true },
 9  user: { read: true, write: false },
10  guest: { read: false, write: false },
11};
12
13console.log(rolePermissions);
  • Record<K, T> is useful when you want to define key-value pairs. In the example above, permissions are defined based on user roles.

Pick<T, K>

Pick<T, K> extracts only specified properties from an object type. You can create a new type by extracting only the necessary properties.

 1interface User {
 2  id: number;
 3  name: string;
 4  age: number;
 5}
 6
 7type UserSummary = Pick<User, "id" | "name">;
 8
 9const summary: UserSummary = {
10  id: 1,
11  name: "Alice"
12};
13
14console.log(summary);
  • By using Pick<T, K>, you can extract specific properties from an object type and treat them as a new type. For example, only id and name are extracted in the example above.

Omit<T, K>

Omit<T, K> excludes specified properties from an object type. This is the opposite operation of Pick.

 1interface User {
 2  id: number;
 3  name: string;
 4  age: number;
 5  email: string;
 6}
 7
 8type UserWithoutEmail = Omit<User, "email">;
 9
10const userWithoutEmail: UserWithoutEmail = {
11  id: 1,
12  name: "Alice",
13  age: 25
14};
15
16console.log(userWithoutEmail);
  • By using Omit<T, K>, you can create a new type by excluding specified properties. In the example above, the email property is excluded.

Exclude<T, U>

Exclude<T, U> creates a new type by removing type U from union type T. It is used when you want to remove a specific type.

1type Status = "active" | "inactive" | "pending";
2type ExcludedStatus = Exclude<Status, "pending">;
3
4const userStatus: ExcludedStatus = "active"; // "pending" is excluded, so it cannot be chosen
5console.log(userStatus);
  • By using Exclude<T, U>, you can remove unnecessary types within a union type. In the example above, since "pending" is excluded, only "active" or "inactive" can be selected.

Extract<T, U>

Extract<T, U> extracts the portions that match type U within union type T. It is useful when you want to extract only a specific type.

1type Status = "active" | "inactive" | "pending";
2type ActiveStatus = Extract<Status, "active" | "pending">;
3
4const userStatus: ActiveStatus = "active"; // "inactive" cannot be chosen
5console.log(userStatus);
  • Extract<T, U> performs the opposite operation of Exclude. In the example above, only "active" and "pending" can be selected.

NonNullable<T>

NonNullable<T> creates a type with null and undefined excluded. It is helpful when you want to exclude these values from optional types.

1type UserName = string | null | undefined;
2type ValidUserName = NonNullable<UserName>;
3
4const userName: ValidUserName = "Alice"; // null and undefined cannot be chosen
5console.log(userName);
  • NonNullable<T> excludes null and undefined, ensuring that a value is present.

Conclusion

TypeScript utility types are powerful tools for making type definitions more concise and flexible. Understanding and appropriately using basic utility types like Partial, Required, Readonly, Record, etc., can enhance code reusability and maintainability. Mastering these types allows for more robust and safe type definitions, enabling efficient development.

TypeScript's keyof Operator

TypeScript's keyof operator is used to retrieve all property names of an object type. Using this operator, you can obtain the keys of an object type as a union type. This is extremely useful for writing type-safe code.

Basic Usage

1interface Person {
2    name: string;
3    age: number;
4    email: string;
5}
6
7// Use keyof to get the property names of Person
8type PersonKeys = keyof Person; // "name" | "age" | "email"

Example Usage

Using it in Function Arguments

You can use keyof to define a function that has types based on specific properties of an object.

 1function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
 2    return obj[key];
 3}
 4
 5const person: Person = { name: "Alice", age: 30, email: "alice@example.com" };
 6const userName = getProperty(person, "name"); // type is string
 7const age = getProperty(person, "age");   // type is number
 8
 9console.log("Name:", userName);
10console.log("Age:", age);
  • This function retrieves a property from an object in a type-safe way based on the specified key.

Enhancing Type Constraints

By using keyof, you can ensure that the keys passed to a function are validated at compile time.

1// Passing an invalid property name results in an error
2const invalid = getProperty(person, "invalidKey"); // Error
  • This code demonstrates that specifying a non-existent property name will result in a compile-time error.

Summary

  • The keyof operator is used to retrieve all the property names of an object type.
  • You can obtain the property names as a union type, achieving type-safe code.
  • By using it in function arguments, you can restrict the function to accept only valid property names.

In this way, the keyof operator enhances type safety in TypeScript and helps you write more robust code.

You can follow along with the above article using Visual Studio Code on our YouTube channel. Please also check out the YouTube channel.

YouTube Video