Thinking in TypeScript
On the Office Online team, we regularly hire experienced engineers that have never worked on large scale web applications. As a result, we have a large number of engineers with strong backgrounds in languages like C# and Java learning how to write code in TypeScript for the first time. This transition comes naturally to most engineers, as some of the syntax may feel very familiar to C# and Java developers, but I think there is a lot to be gained from spending some time to learn how to leverage some of the unique capabilities of the language. This blog post will focus on some of the tips we’ve often found ourselves sharing in code reviews for developers that are new to TypeScript. I hope you find them helpful!
You might not need a class
Developers with a background in Object Oriented programming languages are used to encapsulating code within classes. An example we see regularly from new developers is the creation of utility classes that contains some static methods. Consider this example:
While this is a perfectly reasonable way to organize your code, you could instead create a module that exports a set of utility functions:
An interface might not be what you expect it to be
A common source of confusion for developers new to TypeScript is the concept of interfaces. Developers coming from a C# or Java background are familiar with the concept of interfaces used in a context like this:
The types don’t exist at run time
While the inability to access type information at run time may seem like a deal-breaking constraint, TypeScript supports Discriminated Unions as a mechanism for differentiating objects of different types at run time. To better understand how discriminated unions work, let’s consider an application that displays a number to the user and supports a set of actions that manipulates that number.
In this example, we have a function getNextState that takes in an Action and the current state and returns the next state. Action is a union type of the Add and Decrement interfaces. Both of these interfaces have a type property, referred to as the discriminant, that allows us to determine which Action type was passed in to the getNextState function.The TypeScript compiler is able to leverage this type property within each clause of the switch statement to narrow down the set of valid properties to just those that correspond to the interface with that type value. For example, the compiler knows that the action inside the ‘Add’ case has a numberToAdd property and it knows that any reference to numberToAdd inside of the ‘Decrement’ block is a compiler error. The use of a discriminant allows us to differentiate objects of different types at runtime while also providing us with stronger type checking and an improved intellisense experience. When discriminated unions are combined with TypeScript strict mode, the compiler is able to do exhaustiveness checking on the switch statement inside the getNextState function, which is helpful to ensure that developers update that function as new Action types are added.
Use undefined instead of null
Since both null and undefined are falsy values. However, this approach won’t work if the type of foo also had valid falsy values (such as 0 for a number or “” for a string) that you don’t want to handle in the same fashion as null and undefined. As a result, we found it preferable on the Office Online team to avoid using null in our codebase, instead encouraging the use of undefined (almost) everywhere.