Type guards
Normally in JavaScript, when we're unsure of a value's type, we check the existance of one of its members to determine what it is:
This code will throw the following TypeScript errors:
Property 'toUpperCase' does not exist on type 'string | number'
Property 'toUpperCase' does not exist on type 'number'
Property 'toFixed' does not exist on type 'string | number'
Property 'toFixed' does not exist on type 'string'
We're getting these errors because we can only access members common to all types in a union, which id
is. So, since id
could be a string
or a number
, and since neither of the two share the toUpperCase
or toFixed
members, we get those errors.
If, however, we wanted to run id.toString()
, we'd be fine, since both number
and string
have the toString
method.
If we assert, however, that what we're checking is a string
or a number
, then we eliminate the errors:
Now that we've told TypeScript that in the case of a string
, we want to check for the existence of the toUpperCase
member and in the case of a number
we want to check for toFixed
, the complier is happy.
But this kind of granular assertion is extremely verbose and cumbersome. Luckily, we can inform TypeScript once in our first if
statement. And by doing so, TypeScript is intellignet enough to know that by the time we reach the else if
clause, id
must be a number. In order to do this, we need to create a function calleda type guard:
Here, we've taken our if
clause and put it in a function whose return value is a type predicate. Type predicates take the form: parameterName is Type
.
Now, we can use isString
in our code:
Once we've checked id
's type using a type guard, TypeScript can be sure that in the if
clause, id
is a string
and that in the else
clause, it's a number. This will allow it to give you correct autocompletion in your editor and warn you if you try to use an incorrect method or property of id
within the clause's block.
But what if we don't want to create a type guard function just to check a value's type? Fear not, there is a simpler way!
TypeScript recognizes the use of both typeof
and instanceof
as type guards. Allowing us to rewrite our code like so:
The only acceptable string values that can be used in a typeof
type guard are "string"
, "number"
, "boolean"
, and "symbol"
.
We can also use instanceof
in case we're dealing with instances of a class and not primitive values:
In this code, getDate()
will give us either a Date
object or a number representing the last two digits of the current year. If we try to add 2000
to a Date
object, we get the following TypeScript error:
Operator '+' cannot be applied to types '2000' and 'Date'.
We need to figure out what type we're dealing with, and for that we use instanceof
. Once we've narrowed down the value of dt
to being an instance of Date
, we can safely call getFullYear()
on it, or add 2000
to the value in the the else
clause.
Last updated