pet is Fish ‐ type predicates - kdaisho/Blog GitHub Wiki

// set up
class Fish {
  swim() {}
}

class Bird {
  fly() {}
}

function getSmallPet() {
  if (Math.random() > .5) {
    return new Fish()
  } else {
    return new Bird()
  }
}

With type predicates

Type predicates: pet is Fish is not a function return type. It ensures the parameter type is Fish after the function call.

// Type predicate narrows the parameter pet after this function call
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

// application
const pet = getSmallPet();
 
if (isFish(pet)) {
  pet.swim();
} else {
  pet.fly();
}

Without type predicates

Without type predicates, type guards like 'swim' in pet are required before use.

// no type predicates
function isFish(pet: Fish | Bird) {
  return (pet as Fish).swim !== undefined;
}

// application
const pet = getSmallPet();
 
if (isFish(pet)) {
  pet.swim();
    // ^ Property 'swim' does not exist on type 'Bird'.
} else {
  pet.fly();
    // ^ Property 'fly' does not exist on type 'Fish'.
}

Type predicates simplify type checks, ensuring the correct usage of properties without the need for additional guards.

Use with caution.

Type predicate functions provide a convenient and DRY way to perform runtime type checks. However, they can potentially mislead TypeScript's type checker, similar to type casting.

Consider the example below, isRegistrationOptionsData doesn't perform any actual checks but always returns true. This leads TypeScript to assume that any object passed to this function is of the specified type, even though this might not be the case.

function isRegistrationOptionsData(arg: object): arg is {
    data: { email: string; registrationOptions: PublicKeyCredentialCreationOptionsJSON }
} {
    // nothing is checked here
    return true
}