31.3 C
New York

Typescript type narrowing

Date:

Many times we need to be able to differentiate between Typescript interfaces which have some properties in common. Take the following example:

interface Vehicle {
  weight: number;
  wheels?: number;
  class?: 'A' | '1' | '2' | '3';
}

In our application we have different vehicles. For trucks, for example, we want to keep track of their weight, and their number of wheels. However other vehicles (like yatches) have no wheels, but have other attributes we want to know, like for instance their class, which is a string that can take values ‘A’, ‘1’, ‘2’, or ‘3’.

We might think that a good configuration can be as follows:

interface Truck {
  weight: number;
  wheels: number;
}

interface Yatch {
  weight: number;
  class: 'A' | '1' | '2' | '3';
}

export type Vehicle = Truck | Yatch;

However, with this setup we will have a problem when trying to use Vehicle objects:

Typescript type narrowing Code CoinGenius Hosts Virtual Crypto Event The Road To Mass Adoption

Typescript type narrowing Code CoinGenius Hosts Virtual Crypto Event The Road To Mass Adoption

As we can see, Typescript can’t decide whether the input object is a Truck or a Yatch, and it throws the following error:

Property 'wheels' does not exist on type 'Yatch'

How can we narrow the object’s type? There are at least 3 alternatives:

1. Create a new type property on each of the interfaces

interface Truck {
  weight: number;
  wheels: number;
  type: 'truck';
}

interface Yatch {
  weight: number;
  class: 'A' | '1' | '2' | '3';
  type: 'yatch';
}

export type Vehicle = Truck | Yatch;

This is known as discriminated unions in Typescript. This solution would be the most adequate if our interfaces already had the type attribute beforehand.

When using Vehicle objects, now we can use a switch case to differentiate both interfaces:

Typescript type narrowing Code CoinGenius Hosts Virtual Crypto Event The Road To Mass Adoption

If we are using classes instead of interfaces for our types, the following alternative syntax produces the exact same result:

class Truck {
  weight: number;
  wheels: number;
  readonly type = 'truck';
}

class Yatch {
  weight: number;
  class: 'A' | '1' | '2' | '3';
  readonly type = 'yatch';
}

export type Vehicle = Truck | Yatch;

2. Use type guards

Type guards are functions that help with type narrowing.

interface Truck {
  weight: number;
  wheels: number;
}

interface Yatch {
  weight: number;
  class: 'A' | '1' | '2' | '3';
}

export type Vehicle = Truck | Yatch;

export function isTruck(arg: any): arg is Truck {
  return !!arg.weight && !!arg.wheels;
}

export function isYatch(arg: any): arg is Yatch {
  return !!arg.weight && !!arg.class;
}

If isTruck(object) returns true, it means that our object is indeed a Truck. We can import and use these functions anywhere in our application:

Typescript type narrowing Code CoinGenius Hosts Virtual Crypto Event The Road To Mass Adoption

However, there is a small problem with this setup: we can still build Vehicle objects which are not Truck nor Yatch:

Typescript type narrowing Code CoinGenius Hosts Virtual Crypto Event The Road To Mass Adoption

3. Use “never”

In order to fix this last problem we can use the never type, introduced in Typescript 2.0. If an interface has a property of type never, then that property can not be defined on any object which follows that interface.

interface Truck {
  weight: number;
  wheels: number;
  class?: never;
}

interface Yatch {
  weight: number;
  wheels?: never;
  class: 'A' | '1' | '2' | '3';
}

export type Vehicle = Truck | Yatch;

Type guards work exactly as they previously did, but now we can’t create Vehicle objects which have at the same time both the wheels and class properties:

Typescript type narrowing Code CoinGenius Hosts Virtual Crypto Event The Road To Mass Adoption

Typescript type narrowing Code CoinGenius Hosts Virtual Crypto Event The Road To Mass Adoption

As we can see we obtain the following error:

Type '{ weight: number; wheels: number; class: "A" }' is not assignable to type 'Vehicle'.
Types of property 'wheels' are incompatible.
Type 'number' is not assignable to type 'never'.

Check this post and many others at my blog, cybertricks.blog!

  • Coinsmart. Europe’s Best Bitcoin and Crypto Exchange.Click Here
  • Platoblockchain. Web3 Metaverse Intelligence. Knowledge Amplified. Access Here.
  • Source: https://www.codementor.io/davidsilvasanmartin/typescript-discriminated-unions-1jhqp24mvx

This Post was originally published on Codementor Angular

Related articles

spot_img

Recent articles

spot_img