In this article I will show you how to write safer TypeScript code by leveraging a feature called strictNullChecks. It’s quite easy to miss because it’s turned off by default, but can help a lot to produce more robust code. I’ll also briefly introduce another lesser-known feature of the language called type guards.
To experiment with these tradeoffs, I decided to use TypeScript for a new React/Redux project. The application is a web SPA which is the front end for a typical SAAS. Users can register/login, adding credit cards, managing api keys, see billing information, etc. All the examples in this article will be from that project and have React+Redux context.
Uncaught ReferenceError: foo is not defined Uncaught TypeError: window.foo is not a function
These errors happen at runtime when you’ve deployed your code already. Ideally, we want to prevent them when writing the code, not when we are running it. That’s what strictNullChecks compiler flag is for.
Ideally, if you read the TypeScript manual before coding, you’d know about this flag already. In my case I was learning TypeScript code-first and only learnt about it after the fact. So, my first suggestion would be this: don’t be me, RTFM first! The TypeScript handbook is really tiny and handy. You can finish it in 2-3 days.
Strict null checks
strictNullChecks protects you from referencing nulls or undefined values in code. It can be enabled by adding the –strictNullChecks flag as an option to the command-line compiler, or adding it to tsconfig.json file.
The TypeScript developers actually encourage us to turn this option on. From the handbook:
As a note: we encourage the use of –strictNullChecks when possible, but for the purposes of this handbook, we will assume it is turned off.
It’s a good idea to enable it when your project starts. Otherwise you can end up with hundreds of compiler errors when you finally turn it on. By the time I got around to it enabling it, it took me about 3 hours to fix all of the resultant errors.
Let’s have a look at an example that will fail during compilation when flag is on (it compiles fine when flag is off):
The compiler gives us an error because `ownProps` is an optional argument and could be undefined. So this is the point where compiler is telling us: “Dude, watch for undefined!”.
To fix the problem we can use an upcoming ECMAScript feature called object spread that is implemented in TypeScript 2.1:
If `ownProps` is undefined then it will be just ignored. The important point is that we are prevented from accessing an object that could be undefined. Alternately, we would explicitly check `ownProps` to be defined (in an if condition) without object spreading syntax.
The next example shows how strictNullChecks could help not only make code safer but also reduce some boilerplate:
The compiler is failing on line 17 because we are trying to use a `newState` variable, but it’s possibly was never assigned (ah, that sweet uninitialised identifier). So to fix the error we can assign `newState` to `state` from the passed in arguments, and remove the `if` condition entirely:
That said, in some cases you want to use null or undefined values. For instance:
So in this case, returning undefined is a valid behaviour of the function. However, the compiler generates an error. To work around it, we need to explicitly add undefined to the function declaration to let the compiler know that it’s a valid case:
Now, if we try to use the function without checking the result, we will get additional errors. For example:
So in this case we should check that the return value is defined:
NOTE: I found that using undefined instead of null is more convenient, because TypeScript uses undefined in place of optional interfaces fields and function arguments.
Type guards are another TypeScript feature that allows us to check types and automatically resolve them.
Consider this simple function that converts bytes to megabytes:
We’d like that if `this.props.usedBytes` is undefined, then usedMb() will return undefined. However, our current implementation won’t work as expected if `this.props.usedBytes` is assigned a zero value, as we’ll get undefined as a returned value when what we actually want is zero. You’d have to rely on unit tests to pick something up like that, but what’d be nice is we could check that the type of the property is a number.
Fortunately, TypeScript provides nice functionality to do that. It’s called a type guard. In our case it’s just a simple function that looks like this:
By having a return signature of the form ” is “, we’re telling the compiler to consider our function to be a type guard.
Then we can rewrite our original usedMb() function to use our type guard:
That said, using ‘any’ as an argument type for a type guard function is actually not that common. Type guards are more powerful when you can declare a range of passing types. The compiler can then distinguish between those types during usage. Consider this example from the handbook:
As you can see compiler knows that pet is of type Bird when goes to else statement.
TypeScript provides a couple of handy mechanisms for making your code safer.
In particular, I have found the ‘–strictNullChecks’ option to be very useful. It’s highlighted actual problems with my code, without generate any noise. I would highly recommend that you turn it on if you haven’t already. Also, using it tends to make your code not only safer but also smaller and more elegant.
Type guards are also an interesting feature, although to be honest I have less real-world experience with them.
Using these features takes some of the pressure off of your unit test suite to cover all possible scenarios. This doesn’t mean they should replace unit tests. Instead, they can complement them, especially when performing large refactorings.