In this article I’ll go over a few key highlights from the recent release of TypeScript 3.7. I’ll be looking at things through the lens of how they impact software and code quality, because let’s face it – that’s kind of my jam.
I’ve been following TypeScript releases for some time now. I’ve seen the last few come and go without anything that significantly impacts my day to day life. A minor release like 3.7 doesn’t sound very big or important, but this one is huge in the way it can improve code quality.
While TypeScript 3.7 has a surprising number of things in it, I’ll be writing specifically about the following changes:
- Optional Chaining
- Nullish Coalescing
- Assertion Functions
- The Declare Keyword
- Uncalled Function Checks
Let’s get started.
Optional chaining is a form of syntax to short circuit evaluations in the event that something is null or undefined.
TypeScript introduces the
?. operator to optionally invoke something on the condition that the object exists.
Take a look at the comparison below between the old and new way of doing things:
We’ve had optional chaining in C# in the .NET world for some time, and I’m a huge fan of it.
What I love about this is that:
- It makes syntax incredibly brief, but very readable
- It makes it easy to check for nulls
Both of these things help a lot with both code and software quality. If I’m reviewing code and I’m not being distracted by extra syntax for if blocks, I’m able to focus on the code that actually matters.
Similarly, if I’m a developer working on a method that’s longer than it should be, I might get lazy and make an assumption that a variable has been checked for null already. This sounds silly, but I’ve felt the temptation myself of not wanting to get out of the flow and go up a line to add a null check.
Being able to quickly and conditionally check for null is going to help quality more than you might initially think.
Nullish coalescing refers to the use of the
?? operator in evaluating things that could potentially be null or undefined.
null which is separate to but related to
undefined. The distinction of course, is something that is
null is explicitly nothing but something
undefined has literally not been defined to have any value. The perils of dynamic languages, I suppose.
For example of nullish coalescing take a look at the following before and after snippet:
As you can see, using nullish coalescing is very clean and simple compared to the ternary (
?) operator of equivalent if checks with assignments.
What I like about this is similar to what I like about optional chaining – it helps you focus on the code that actually matters.
If we as engineers can eliminate extra noise in our code and syntax, we’re going to spot defects easier and earlier.
Assertion functions I’m more on the fence about. Essentially they are functions which, if called without error, have asserted something to TypeScript’s internal type interpreting code. This in turn allows the compiler to catch more specific issues based on the facts now proved to be true.
Let’s look at an example:
Here we have a
getStandardFixedNumberString function that takes in a value that is known to either be a
string or a
toFixed is not available on a
string type, this code is not normally permissible.
assertIsNumber function defines an assertion clause that essentially says “if this didn’t error, what the function asserts is true and can be understood for the rest of your method”.
Since we assert that input is a number, in this case, the functions available to numbers become available and so TypeScript has no problems with our
So, here’s where I am on this one: if your methods are long enough that you need assertion functions, you should probably split those up into smaller methods.
You could argue that assertion functions are a way of getting TypeScript to do some runtime type checking instead of the standard static checking it does at compile time only.
However, I don’t think that TypeScript thrives by enforcing rules at runtime. In my opinion, we should enforce our typings at compilation and then validate external input to the system at the edges. Things like API calls and user input should be asserted and cast, not your main application code.
Still, assertion functions are something to consider and watch as they potentially serve other uses down the road.
The Declare Keyword
Declare lets us combine the dynamic typing system with inheritance to essentially re-declare inherited properties.
Take a look at the following simple hierarchy:
Here we have a
DarkTheme that inherits from
Theme declares a collection of
Person entities, which is itself an abstraction.
Because we know all people who use dark themes are awesome, we know that the
users property will also only have
With TypeScript 3.7, TypeScript can understand this too.
We use the
declare keyword to tell TypeScript to make assumptions about something without emitting anything particular for this assumption. Previously I’ve used
declare to reference things like external libraries loaded on shared web pages.
Here we’re using
declare to specify that a property has different typings in that context than previously defined.
I really like this feature. While not as commonly used as other language features, this helps team with complex hierarchies to understand their properties and not need to make type assertions.
Uncalled Function Checks
Finally, TypeScript now will catch a common error we frequently make with functions. Take a look at the following code:
Here we meant to invoke
person.onlyDoesBoringThings at line 10, but forgot the
()‘s and are instead evaluating the function against null / undefined. The function is defined, so that condition evaluates as
true even though invoking it would have returned
TypeScript 3.7 catches this error out of the box:
This condition will always return true since the function is always defined. Did you mean to call it instead?
This simple built-in check should improve your quality with no extra steps needed and so I’m all for it.
Next Steps with TypeScript 3.7
If you’d like to learn more about these features or other improvements to TypeScript, take a look at the full release notes.
You can update to TypeScript 3.7 via npm by running
npm update -g typescript.
What do you think of these changes? What are you most excited about? Do you have a compelling reason to use assertion functions I’ve not thought of?