Write explicit type guards
August 25, 2020
Inside a function with arguments, you may want to write a guard to check that a parameter has been set before using it.
Example:
function myMethod(foo: ?string) {
if (!foo) {
return;
}
console.log(`The value of the foo string is: ${foo}`);
}
Great! We checked the argument before trying to use it, making Flow/TypeScript happy and adding resilience to our code.
In some cases, this is all you need - and there’s nothing wrong with this!
So when is this not ok? Read on…
The dangers of type coercion
This approach of if (foo)
above relies on type coercion from a stringy value to a boolean value.
But what if our input is the empty string? Empty strings are falsey, so our function wouldn’t print anything - maybe not what we wanted! This problem also affects numbers, boolean arguments, or anything else that could coerce to falsey.
Let’s say we do want to allow for empty strings - we’ll need to try something else. Maybe we could use ===
and check explicitly for null
?
function myMethod(foo: ?string) {
if (foo === null) {
return;
}
console.log(`The value of the foo string is: ${foo}`);
}
type !== undefined
What if foo
was undefined
? That would pass the above guard and break our code, since undefined !== null
! So we also need to check for that:
function myMethod(foo: ?string) {
if (foo === null || foo === undefined) {
return;
}
console.log(`The value of the foo string is: ${foo}`);
}
Perfect - our guard now behaves as expected!
Bonus: Check for types explicitly
if (foo === null || foo === undefined)
is a bit tedious to write out each time.
Rather than disallowing a known set of bad types (undefined
, null
), we can do even better and only allow the type that you want:
function myMethod(foo: ?string) {
if (typeof foo !== 'string') {
return;
}
console.log(`The value of the foo string is: ${foo}`);
}
This avoids any type coercion issues, and provides an extra runtime check that your inputs are the type you expect.
There’s not a huge amount of downside here, and explicitness is never usually a bad thing… If in doubt, go for this option.
Takeaway
- remember that
undefined !== null
. 90% of the time you’ll want to guard against both values. - even better is to explicitly check for the type you do want, vs checking for types you don’t want.
Reference
Here’s a list of examples for how to check for various types:
Type | Check |
---|---|
Number |
typeof myVar === 'number' |
String |
typeof myVar === 'string' |
Array |
Array.isArray(myVar) |
Date |
myVar instanceof Date |
Object |
typeof myVar === 'object' |
Further Reading
Discuss on Twitter • Edit on GitHub