This article will cover Type Predicates at a high level. To understand Type Predicates, we must first understand how they are used in relation to union types.
Union Types
In Typescript, a variable isn't always restricted to a single type. Union Types are a way to declare multiple types onto a single value.
// value can now be set to a `string`, `boolean`, or `null` value.
let value: string | boolean | null = ...
interface Cat {
numberOfLives: number;
}
interface Dog {
isAGoodBoy: boolean;
}
let animal: Cat | Dog = ...
When we use union types, we have to do work to narrow the possible types down to the current value's actual type. Type Guards are what allow us to do this narrowing.
Type Guards
According to the official docs,
A type guard is some expression that performs a runtime check that guarantees the type in some scope.
Put another way, type guards guarantee that a string is a string when it could also be a number.
Type guards are not entirely different than doing feature detection. The big idea is to try to detect properties, methods or prototypes to figure out how to handle a value. There are four main ways to use type guards:
-
in
keyword -
typeof
keyword -
instanceof
keyword - type predicates with custom type guard
Type Predicate
While you are probably familiar with "in", "typeof", and "instanceof", you might be wondering what "type predicates" are. Type predicates are a special return type that signals to the Typescript compiler what type a particular value is. Type predicates are always attached to a function that takes a single argument and returns a boolean. Type predicates are expressed as argumentName is Type
.
interface Cat {
numberOfLives: number;
}
interface Dog {
isAGoodBoy: boolean;
}
function isCat(animal: Cat | Dog): animal is Cat {
return typeof animal.numberOfLives === 'number';
}
For sample function, isCat
, is executed at run time just like all other type guards. Since this function returns a boolean and includes the type predicate animal is Cat
, the Typescript compiler will correctly cast the animal
as Cat
if isCat
evaluates as true. It will also cast animal
as Dog
if isCat
evaluates as false.
let animal: Cat | Dog = ...
if (isCat(animal)) {
// animal successfully cast as a Cat
} else {
// animal successfully cast as a Dog
}
Pretty neat! Perhaps the best thing about custom type guards and type predicates is not only we can use in
, instanceof
, and typeof
in our type guards but we can also custom type checks. As long as our function returns a boolean, Typescript will do the right thing.
Top comments (5)
Thanks for the type predicate explanation. But in my case TS (v.3.8.3) is not happy with
isCat
function:What are other options for
isCat
implementation? I can think ofThis works, but
numberOfLives
is a plain string. It's easy to make a typo or forget to change this string in the process of refactoring. Is there a better option to create the type predicate?All right, I found that TS has discriminated unions for such cases.
Just extend your interfaces with a common property and use it to narrow types in your code.
I use the "as" keyword and then a specific property on the type.
so in your above example you could leave out the "kind" property:
I like not having to add the extra property to my types. :)
Full code for my example found in this file: github.com/mirkoRainer/RulesLawyer...
Would be good to cover if we can create
Predicate
type which will infer type based on some value in it:Maybe sounds like another article
great and simple explaination. thanks