Hello everyone, Today, In this article, we will go through some beneficial and essential utility types in Typescript that can make your life easier.
Utility types in Typescript are some predefined generic types that can be used to manipulate or create other new types. These types are available globally in all Typescript projects, so you don't need to add any dependencies to get these going.
Table of contents
- Partial
- Required
- Omit
- Pick
- Multiple utility types together
- Readonly
- Mutable
- Exclude
- Extract
- ReturnType
- Parameters
- NonNullable
- Awaited
- Awaited and ReturnType combined
- Conclusion
Partial
The first utility type we will look at is Partial,
which, as It sounds, makes everything optional or partial.
Here is an example of Partial utility type:
interface Person {
name: string;
age: number;
email: string;
}
// Define a new type called 'PartialPerson' that is a partial version of 'Person'
type PartialPerson = Partial<Person>;
// Same as:
// interface Person {
// name?: string | undefined;
// age?: number | undefined;
// email?: string | undefined;
// }
Required
The opposite to Partial
is Required
utility type, which makes everything required.
interface Person {
name?: string | undefined;
age?: number | undefined;
email?: string | undefined;
}
// Define a new type called 'RequiredPerson' that is a required version of 'Person'
type RequiredPerson = Required<Person>;
// Same as:
// interface Person {
// name: string;
// age: number;
// email: string;
// }
Omit
You can use the Omit
utility type to create a new type from an existing type, however, with some properties removed.
interface User {
id: number;
name: string;
email: string;
age: number;
}
type UserWithoutEmail = Omit<User, 'email'>;
// same as:
// interface User {
// id: string;
// name: string;
// age: number;
// }
We can also remove multiple properties by passing an union
interface User {
id: number;
name: string;
email: string;
age: number;
}
type UserWithoutEmailAndName = Omit<User, 'email' | 'name'>;
// same as:
// interface User {
// id: string;
// age: number;
// }
Pick
The opposite of Omit
is the Pick
utility type that allows you to create a new type that contains only a subset of properties from an existing type.
interface User {
id: number;
name: string;
email: string;
age: number;
}
type UserWithEmailAndName = Pick<User, 'email' | 'name'>;
// same as:
// interface User {
// name: string;
// email: string;
// }
Multiple utility types together
We can even use multiple utility types together. For example:
interface User {
id: number;
name: string;
email: string;
age: number;
}
type PartialPick = Partial<Pick<User, 'email' | 'name'>>;
// same as:
// interface User {
// name?: string | undefined;
// email?: string | undefined;
// }
Another exmaple:
interface User {
id: number;
name: string;
email: string;
age: number;
}
type OmitPartialPick = Omit<Partial<Pick<User, 'email' | 'name'>>, 'email'>;
// same as:
// interface User {
// name?: string | undefined;
// }
Readonly
Readonly
utility types allow you to create a new type from an existing type set as readonly,
which means we cannot modify any property after the initialization.
interface Person {
id: number;
name: string;
age: number;
}
type ReadonlyPerson = Readonly<Person>;
// same as:
// interface Person {
// readonly id: number;
// readonly name: string;
// readonly age: number;
// }
const person: ReadonlyPerson = {
id: 1,
name: 'John',
age: 25
};
person.name = 'Mike'; // Error: Cannot assign to 'name' because it is a read-only property.
Mutable
You can also create a Mutable
type helper that allows you to convert all readonly types to mutable type.
interface Person {
readonly id: number;
readonly name: string;
readonly age: number;
}
// The syntax for Mutable is as follows:
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
type MutablePerson = Mutable<Person>;
// same as:
// interface Person {
// id: number;
// name: string;
// age: number;
// }
const person: MutablePerson = {
id: 1,
name: 'John',
age: 25
};
person.name = 'Mike'; // Okay
person.id = 2; // Okay
Exclude
Exclude
utility type allows you to create a new type by removing members of an union
type NumberOrString = number | string;
type OnlyNumber = Exclude<NumberOrString, string>;
// same as:
// type OnlyNumber = number;
const num: OnlyNumber = 5;
const str: OnlyNumber = 'hello'; // Error: Type '"hello"' is not assignable to type 'number'.
You can even exlude
mulitple members of an union:
type NumberStringOrBoolean = number | string | boolean;
type OnlyBoolean = Exclude<NumberStringOrBoolean, string | number>;
// same as:
// type OnlyBoolean = boolean;
Extract
The opposite to Exclude
is Extract
utitlity type that allows you to pick a or multiple members from an union:
type NumberOrString = number | string | boolean;
type OnlyNumber = Extract<NumberOrString, number>;
// same as:
// type OnlyNumber = number;
ReturnType
ReturnType
utility type lets you to extract the return type of a function type. It takes a function type as an argument and returns the value type that the function returns.
function add(a: number, b: number): number {
return a + b;
}
type AddReturnType = ReturnType<typeof add>;
// type AddReturnType = number;
// ---
function addStr(a: string, b: string): string{
return a + b;
}
type AddReturnType2 = ReturnType<typeof addStr>;
// type AddReturnType2 = string;
Parameters
The Parameters
utility type lets you extract the type of parameters from a function.
function add(a: number, b: string, c:boolean): string {
return a + b;
}
type AddReturnType = Parameters<typeof add>;
// type AddReturnType = [a: number, b: string, c:boolean];
NonNullable
NonNullable
utility type lets you to create a new type by excluding null
and undefined
from a given type.
type NullableString = string | null | undefined;
type NonNullableString = NonNullable<NullableString>;
// type NonNullableString = string;
const str1: NullableString = null;
const str2: NonNullableString = 'hello';
const str3: NonNullableString = null; // Error: Type 'null' is not assignable to type 'string'.
Awaited
Awaited
utility type allows you to extract the resolved type of a promise
or other type that uses await
.
type promiseNumber = Promise<number>
type justNumber = Awaited<Promise<number>>
// type justNumber = number
Awaited and ReturnType combined
Here's an example of using ReturnType
and Awaited
together:
async function fetchData(): Promise<string> {
// fetch data from API and return a string
}
type ResolvedResult = Awaited<ReturnType<typeof fetchData>>;
// type ResolvedResult = string
In this example, we define an async function that returns a Promise
of a string (Promise<string>).
We then use ReturnType
to extract the return type of fetchData and pass it as an argument to Awaited
to unwrap the promised's resolved type.
Conclusion
These are some of the most used typescript utility types that are heavily used by other developers worldwide. It cleans your code and can be used to work with types more expressively and concisely. I hope you will find this article helpful, and if you think I missed any essential utility types, please let me know in the comments. Thanks for reading the article. See you all in my next article🐼.
Visit:
👨💻My Portfolio
🏞️My Fiverr
🌉My Github
Top comments (16)
You are a great developer surly
Thanks for your nice comment😊.
Concise and to the point. I foresee a terrific developer in you...
Great post!
May I translate this article into Japanese and post it to community? (I'll definitely link this)
Sure, Go ahead!!!
Take your TypeScript skills to new heights with "Mastering TypeScript Core Utility Types":
📖 Buy on Leanpub
📖 Buy on Amazon
If you’re looking to deepen your understanding of TypeScript utility types like Partial or Readonly, I recently wrote a book, Mastering TypeScript Core Utility Types, that covers these and more with practical examples and exercises. Feel free to check it out! 😊 👉 Here
Hot take: don't use any TS utility types :v
Why? What would be the reason to avoid them?
Over using utility types may lead to code that is difficult to understand and maintain. Especially nesting many types together. I know this is a soft opinion, the point is I saw many projects with very complex type definitions. Here's an example:
I think the main problem here is the overuse of intersections, something that can also be a performance issue.
And when the built in utility types are not enough, then there’s the
type-fest
module with a ton more ones: github.com/sindresorhus/type-festAwesome post! Very helpful and those utility types are just awesome! 🔥
Really good and helpful..!!
Thanks for this article, very easy to understand 😊