Skip to content
Santo Sidauruk

7 Utility in Typescript You Should Know

typescript, javascript2 min read

Typescript has been my go-to language every time I start a new project. Typescript helps us to describe objects and to make inline documentation better. One of the things I love about typescript is it already provide utility function to facilitate common type transformations. This time we are going to talk about some of these utility types that I often use everyday.

1. Partial and Required

Partial<T> is the utility type that I use the most. This type makes all the properties of an interface optional.

1type Cars = {
2 brand: string
3 color: string
4}
5
6const helloCars = ( cars: Partial<Cars> ) => {
7 if(cars.color) {
8 console.log(`That is a ${cars.color} ${cars.brand}`)
9 } else {
10 console.log(`That is a ${cars.brand}`)
11 }
12}
13
14helloCars({ brand: "Honda", color: "yellow"}) // That is a yellow Honda
15helloCars({ brand: "Mercedes" }) // That is a Mercedes

In the example above, we can see that we can call the helloCars function without defining the "colors" property because when we use Partial<Cars>, the object will change its annotation as you can see below.

1// Partial<Cars>
2{
3 brand?: string,
4 color? string
5}

In contrast to Partial<T>, Required<T> actually makes all fields of T required

1// Required<Cars>
2{
3 brand: string,
4 color string
5}

2. Readonly

As the name suggests, Readonly<T> makes all fields in the interface read-only. This means that they become immutable and cannot be reassigned.

1interface Cars {
2 brand: string
3 color: string
4}
5
6const myCars: Readonly<Cars> = {
7 brand: "BMW",
8 color: "white"
9}
10
11// Cannot assign to 'brand' because it is a read-only property.
12myCars.brand = "Datsun"
13// Cannot assign to 'color' because it is a read-only property.
14myCars.color = "yellow"

3. Record type

With Record<K,T> we can construct an object type using a union K as our keys and T as the value. Let's use the fields from the previous example.

1type keys = "brand" | "color"
2
3type Cars = Record<keys, string>
4
5const helloCars = ( cars: Cars ) => {
6 console.log(`You have a nice ${cars.color} ${cars.brand}`)
7}
8
9helloCars({ brand: "BMW", color: "black" }) // You have a nice black BMW

With that union keys, you can add as many keys as you like. This will become a problem when you have a big object and wants to restrict the keys depends on an object. Luckily, we can solve this problem by using the keyof

1interface Cars = {
2 brand: string
3 color: string
4}
5
6const restrictedCars: Record<keyof Cars, string> = {
7 brand: "Mercedes",
8 color: "red",
9 unexpectedKey: "value" // will not allowed since we only allowed 'brand' and 'color'
10}

4. Pick and Omit

There will be a time when you just want to pick some keys from an interface and construct a new one. This is when Pick<I, K> comes into play. Pick<I, K> is a utility type that constructs an interface by picking K fields that are listed in I.

1interface Cars = {
2 brand: string
3 color: string
4 year: number
5}
6
7interface PickCars = Pick<Cars, "color" | "year">
8
9const restrictedCars: PickCars = {
10 brand: "Mercedes", // will error since we just pick 'color' and 'year' from Cars
11 color: "red",
12 year: 2020
13}

And there is Omit<I, K> that does the exact opposite of Pick<I,K, Omit<I,K> constructs an interface by picking all fields in I and then remove K keys.

1interface Cars = {
2 brand: string
3 color: string
4 year: number
5}
6
7interface OmitCars = Omit<Cars, "brand">
8
9const restrictedCars: OmitCars = {
10 brand: "Mercedes", // will error since we omit 'brand' from Cars
11 color: "red",
12 year: 2020
13}

The 2 examples above will return the same interface since they contain "color" and "year" in our constructed type.


5. Extract and Exclude

What if we want to Pick and Omit but on union type? Luckily, typescript provides a similar behavior with Extract and Exclude.

Extract<T, U> constructs a type by picking types from T that are a subset of U. However, Exclude<T, U> do the opposite by excluding types from T that are a subset of U. Let's see a few examples.

1type AllDrinks = "teh" | "kopi" | "susu" | "jus" | 99 | (() => string)
2type DrinksWeLike = "teh" | "susu" | "bandrek"
3
4type DrinksExtracted = Extract<AllDrinks, string> // "teh" | "kopi" | "susu" | "jus"
5type AvailableDrinks = Extract<DrinksExtracted, DrinkWeLike> // "teh" | "susu"
6
7type DrinksExcluded = Exclude<AllDrinks, number | Function > // "teh" | "kopi" | "susu" | "jus"
8type DrinksWeHate = Exclude<DrinksExcluded, DrinkWeLike> // "kopi" | "jus"

6. NonNullable

NonNullable<T> is a utility type that we can use to avoid null or undefined.

1type Colors = "green" | "red" | undefined | null
2
3type CorrectColors = NonNullable<Colors> // "green" | "red"

But the interesting part is, this type is only worked if you use —strictNullChecks flag or set strictNullChecks: true on your tsconfig.

For example, this code is using strictNullChecks: false , as you can see the variable can still be assigned to undefined

strictNullChecksFalse

and this is using strictNullChecks: true, the TS compiler will tell you immediately about the error.

strictNullChecksTrue

7. ReturnType

ReturnType<F> returns the return type definition of F. To avoid an error, remember to always fill the F with function. Here is some example.

1type stringF = ReturnType<() => string> // string
2type numberF = ReturnType<() => number> // number
3type unknownF = ReturnType<<T>() => T> // unknown
4type wrongType1 = ReturnType<number> // Error: Type 'number' does not satisfy the constraint '(...args: any) => any'.
5type wrongType2 = ReturnType<Function> // Error: Type 'Function' does not satisfy the constraint '(...args: any) => any'.

Moreover, you also can get a return type of an existing function by utilizing typeof

1function helloCars() {
2 return {
3 brand: "Honda",
4 color: "yellow"
5 }
6}
7
8type F = ReturnType<typeof helloCars>
9// type F = {
10// brand: string;
11// color: string;
12// }

Conclusion

There are many other utility types you can use to help you construct your type but I hope these 7 utilities will help you to start understanding how to use them on your code. Please reach me on twitter on linkedIn if you have any questions or feedback. Hope we'll meet on another article, until then, stay great!