Getting Started With Built-in Utility Types in TypeScript Part 2

Getting Started With Built-in Utility Types in TypeScript Part 2 feature image

TypeScript comes with a number of built-in utility types. These types can be very useful. They can help you create new, and also very advanced, types from existing with only a bit of code. In this article, you will learn about some of these utility types, how they work and also how to use them.

A quick introduction

The goal of this two-part article is to help you understand TypeScript built-in utility types. In this second part, you will learn about the last eight of these types. These are NonNullable, Parameters, ConstructorParameters, ReturnType, InstanceType, ThisParameterType, OmitThisParameter and ThisType.

If you want to learn about the previous eight, take a look at the part 1. Part 1 will help you understand and work with Partial, Required, Readonly, Record, Pick, Omit, Exclude and Extract. Otherwise, keep reading.

Note on availability

The types we will talk about in this part are available in TypeScript version 4.0 and newer. Make sure you use this version to avoid potential issues due to compatibility.

Note on syntax

The utility types in TypeScript we will talk about today all use very similar syntax. It is almost identical. The only difference being the name of each utility type. As we discussed in previous part, utility types can be confusing at first. What can help you understand them is trying to see these types as functions.

This is what these types basically are. They are functions that take some input and return some output. In this case, input and output are types. The rest is just a plain function. The types you will learn about in this part all require a single parameter, some type. You pass this parameter between angle brackets.

These angle brackets, the left- and right-pointing (<>), follow right after the name of the utility type. The name of the type always starts with capital letter. This is very similar to calling a normal JavaScript function. You only swapped the parentheses for brackets and started the function name with a capital letter.

Sometimes, TypeScript utility types require more than one parameter. In that case, you pass these parameters between the angle brackets a usually and separate them with a colon (,). That’s about the syntax. Now, let’s talk about the actual types.

NonNullable

The NonNullable utility type works similarly to the exclude. It takes some type you specified and returns that type excluding all null and undefined types.

// Create a type:
type prop = string | number | string[] | number[] | null | undefined

// Create new type based on previous type
// excluding null and undefined:
type validProp = NonNullable<prop>
// Translates to:
// type validProp = string | number | string[] | number[]

// This is valid:
let use1: validProp = 'Jack'

let use2: validProp = null
// TS error: Type 'null' is not assignable to type 'validProp'.

Parameters

The Parameters type returns a tuple type that contains the types of the parameters function you passed as Type. These parameters are returned in the same order in which they appear in the function. Note that the Type argument, in case of this and following types, is a function ((...args) => type), not a type such as string.

// Declare a function type:
declare function myFunc(num1: number, num2: number): number

// Use Parameter type to create new tuple type
// from parameters of myFunc function:
type myFuncParams = Parameters<typeof myFunc>
// Translates to:
// type myFuncParams = [num1: number, num2: number]

//  This is valid.
let someNumbers: myFuncParams = [13, 15]

let someMix: myFuncParams = [9, 'Hello']
// TS error: Type 'string' is not assignable to type 'number'.


// Use Parameter type to create new tuple type
// from function type:
type StringOnlyParams = Parameters<(foo: string, fizz: string) => void>
// Translates to:
// type StringOnlyParams = [foo: string, fizz: string]

//  This is valid.
let validNamesParams: StringOnlyParams = ['Jill', 'Sandy']

let invalidNamesParams: StringOnlyParams = [false, true]
// TS error: Type 'boolean' is not assignable to type 'string'.

ConstructorParameters

The ConstructorParameters type is very similar to the Parameters type. The difference between these two is that while Parameters gets types from function arguments, the ConstructorParameters gets types from the constructor you pass as the Type argument.

// Create a class:
class Human {
  public name
  public age
  public gender

  constructor(name: string, age: number, gender: string) {
    this.name = name;
    this.age = age;
    this.gender = gender;
  }
}

// Create type based on Human constructor:
type HumanTypes = ConstructorParameters<typeof Human>
// Translates to:
// type HumanTypes = [name: string, age: number, gender: string]

const joe: HumanType = ['Joe', 33, 'male']
const sandra: HumanType = ['Sandra', 41, 'female']
const thomas: HumanType = ['Thomas', 51]
// TS error: Type '[string, number]' is not assignable to type '[name: string, age: number, gender: string]'.
// Source has 2 element(s) but target requires 3.


// Create type based on Human constructor:
type StringType = ConstructorParameters<StringConstructor>
// Translates to:
// type StringType = [value?: any]

ReturnType

The ReturnType is also similar to the Parameters type. Difference here is that the ReturnType extracts the return type of a function you pass as the Type argument.

// Declare type for function:
declare function myFunc(name: string): string

// Use ReturnType to extract return type
// from myFunc type.
type MyFuncReturnType = ReturnType<typeof myFunc>
// Translates to:
// type MyFuncReturnType = string

// This is valid:
let name1: MyFuncReturnType = 'Victoria'

// This is valid:
let name2: MyFuncReturnType = 42
// TS error: Type 'number' is not assignable to type 'string'.

type MyReturnTypeBoolean = ReturnType<() => boolean>
// Translates to:
// type MyReturnTypeBoolean = boolean

type MyReturnTypeStringArr = ReturnType<(num: number) => string[]>
// Translates to:
// type MyReturnTypeStringArr = string[]

type MyReturnTypeVoid = ReturnType<(num: number, word: string) => void>
// Translates to:
// type MyReturnTypeVoid = void

InstanceType

The InstanceType is a bit complicated. What it does is it creates a new type from the instance type of a constructor function you passed as argument for Type. You will probably don’t need this utility type if you work class with a regular class. You can just use the class name to get the instance type you want.

class Dog {
  name = 'Sam'
  age = 1
}

type DogInstanceType = InstanceType<typeof Dog>
// Translates to:
// type DogInstanceType = Dog

// Similar to using the class declaration:
type DogType = Dog
// Translates to:
// type DogType = Dog

ThisParameterType

The ThisParameterType extracts the type of use for the this parameter for the function you passed as the Type argument. If the function doesn’t have this parameter, the utility type will return unknown.

// Create a function with this parameter:
function capitalizeString(this: String) {
  return this[0].toUpperCase + this.substring(1).toLowerCase()
}

// Create type based on capitalizeString function this parameter:
type CapitalizeStringType = ThisParameterType<typeof capitalizeString>
// Translates to:
// type CapitalizeStringType = String

OmitThisParameter

The OmitThisParameter utility type does the opposite of the previous type. It takes a function type as an argument, via the Type, and returns the function type without this parameter.

// Create a function with this parameter:
function capitalize(this: String) {
  return this[0].toUpperCase + this.substring(1).toLowerCase()
}

// Create type based on capitalizeString function:
type CapitalizeType = OmitThisParameter<typeof capitalize>
// Translates to:
// type CapitalizeType = () => string


// Create a function without this parameter:
function sayHi(name: string) {
  return `Hello, ${name}.`
}

// Create type based on sayHi function:
type SayHiType = OmitThisParameter<typeof sayHi>
// Translates to:
// type SayHiType = (name: String) => string

ThisType

The ThisType utility type allows you to explicitly set the this context. You can use it to set this for whole object literals or just a single function. Before you try this, make sure you have the compiler flag —-noImplicitThis enabled.

// Create interface for this in user object:
interface User {
    username: string;
    email: string;
    isActivated: boolean;
    printUserName(): string;
    printEmail(): string;
    printStatus(): boolean;
}

// Create user object and set this type to User interface:
const userObj: ThisType<User> = {
  username: 'Andreas',
  email: 'andreas@foo.com',
  isActivated: false,
  printUserName() {
    return this.username
  },
  printEmail() {
    return this.email
  },
  printStatus() {
    return this.isActivated
  }
}

Combining utility types

One nice thing on TypeScript built-in utility types is that you can also combine them. You can combine one utility type with another utility type. You can also combine one utility type with other types. For example, you can combine types with union or intersection types. This allows you to create even more advanced types.

// Create User interface:
interface User {
  username: string;
  password: string;
}

// Create SuperUser interface:
interface SuperUser {
  clearanceLevel: string;
  accesses: string[];
}

// Create type RegularUser by combining User and SuperUser.
// Make properties in User required
// and properties in SuperUser optional:
type RegularUser = Required<User> & Partial<SuperUser>

// This is valid:
const joeJoe: RegularUser = {
  username: 'joejoe',
  password: 'some_secret_password_unlike_123456',
}

// Also valid:
const jane: RegularUser = {
  username: 'jane',
  password: 'foo_bar_usually-doesnt_work-that_well',
  clearanceLevel: 'A'
}

// This will not work:
const pietro: RegularUser = {
  username: 'pietro'
}
// TS error: Type '{ username: string; }' is not assignable to type 'RegularUser'.
// Property 'password' is missing in type '{ username: string; }' but required in type 'Required<User>'.(

Conclusion: Getting started with built-in utility types in TypeScript part 2

These were the last eight built-in utility types in TypeScript. As you can see, these utility types can help you get a lot of work with types done. This, while making it easier for you and doing all the heavy lifting. I hope that this article helped you understand how these built-in utility types work and how to use them.

If you liked this article, please subscribe so you don't miss any future post.

If you'd like to support me and this blog, you can become a patron, or you can buy me a coffee 🙂

By Alex Devero

I'm Founder/CEO of DEVERO Corporation. Entrepreneur, designer, developer. My mission and MTP is to accelerate the development of humankind through technology.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.