Today’s evening tip is on TypeScript’s custom type guards

What is a type guard

TS “tightens” the Type of the variable within the block-level scope of the statement when it encounters the following conditional statements. This Type inference behavior is called Type Guard.

  • Type judgment:typeof
  • Example judgment:instanceof
  • Attribute judgment:in
  • Literal equality judgment:= =.= = =.! =.! = =

(Here are four commonly used ones)

Type guards can help us obtain more precise variable types in the block-level scope, thus reducing unnecessary type assertions. Here are some concrete examples to help you understand this seemingly abstract concept:

Type judgment:typeof

function test(input: string | number) {
  if (typeof input == 'string') {
    // The type of input "tightened" is string
  } else {
    // The input type is tightened to number}}Copy the code

Example judgment:instanceof

class Foo {}
class Bar {}

function test(input: Foo | Bar) {
  if (input instanceof Foo) {
    // Where input is of type "tightened" to Foo
  } else {
    // The type of input "tightened" is Bar}}Copy the code

Attribute judgment:in

interface Foo {
  foo: string;
}

interface Bar {
  bar: string;
}

function test(input: Foo | Bar) {
  if ('foo' in input) {
    // Where input is of type "tightened" to Foo
  } else {
    // The type of input "tightened" is Bar}}Copy the code

Literal equivalence judgment= =.! =.= = =.! = =

type Foo = 'foo' | 'bar' | 'unknown';

function test(input: Foo) {
  if(input ! ='unknown') {
    / / the type of input here "tightening" as the 'foo' | 'bar'
  } else {
    // The type of input "tightened" is 'unknown'.}}Copy the code

You’ve probably benefited from the convenience of the above “tightening” effect many times in your development, but you just don’t know what to call it. It is worth noting that the type guard is invalidated once the above conditions are not written directly by literals, but are replaced by a conditional function, such as the isString function:

function isString (input: any) {
  return typeof input === 'string';
}

function foo (input: string | number) {
  if (isString(input)) {
    / / the type of input without "tightening" here, is still the string | number
  } else {
    // Same here}}Copy the code

This is because TS can only infer that isString is a function that returns a Boolean value, and does not know what the Boolean value means. In everyday development, however, such “substitution” situations are common for purposes such as optimizing code structure, and custom guards are used in order to continue to gain the inferential power of type guards.

Custom guard

Custom guards use the syntax of {parameter} is {type} to give the above conditional function that returns a Boolean value the ability to type guard. For example, the isString function above could be rewritten as:

function betterIsString (input: any) :input is string { // Change the return type to 'input is string'
  return typeof input === 'string';
}
Copy the code

This gives betterIsString the same guard effect as Typeof Input == ‘string’, with better code reuse.

Since the nature of a custom guard is a type assertion, you can use any logic to determine the type in a custom guard function without being limited to the previous four conditional statements. For example, the following “duck” type guard function assumes that an object must be an instance of “Batman” if it has a helmet, a cape, underwear, and a belt:

class SuperHero { // Superheroes
  readonly name: string;
}
class Batman extends SuperHero { // Batman inherited from superheroes
  private muchMoney: true; // Private a lot of money
}

// Determine whether any object is batman's function
function isBatman (man: any) :man is Batman {
  return man && man.helmet && man.underwear && man.belt && man.cloak;
}

function foo (hero: SuperHero) {
  if (isBatman(hero)) {
    // Hero is batman
  } else {
    // Hero is another superhero}}Copy the code

The proper use of type guards and custom guards in your project can help reduce unnecessary type assertions while improving the readability of your code.

One last question: Besides Batman, can you think of any other superhero with a helmet and a cape and underwear and a belt?

Further reading

Type Guard – Typescript Deep Dive

The original link