TypeScript 3.0. What Has Come?
Nearly two years after the release of version 2.0 and two months from the latest 2.9, Microsoft has released the newest version of TypeScript – the language we use in our projects. Let’s see what changes have been made and how they can affect our daily work.
In TypeScript 3.0 we can identify just five changes affecting the you can use the language. Only one of those is flagged as a breaking change. But that’s enough introduction, let’s see what’s new!
Richer tuple types
Listing 1 Tuples in TypeScript
Starting from TypeScript 3.0 we can define tuples with an undefined maximal number of elements, needing just a minimum. An example is given in Listing 2.
Listing 2 Tuple with undefined maximal number of elements
The first element has a string type, which is mandatory, but the rest, which is just numbers, can be omitted. What’s more, we can even define the tuple without a lower limit (in fact it’s just an array) or an empty tuple. See Listing 3.
Listing 3 Empty tuple and without lower limit
Such tuples have practical uses. For example, we can force an array to have at least one element on the types level. Another use in a more complex context is shown in the next chapter.
Extracting and spreading function arguments with tuple types
Listing 4 Example of using arguments which aren’t defined in a function’s definition
This is fine for the compiler, but when we’re using TypeScript we don’t want to use the “any” type. Let’s say we want to have only strings and numbers provided. Then, we can of course change “any” to “(string | number)”. But this way we are telling the compiler “I want to have an array which has strings or numbers.” We don’t restrict which element is a string, which is a number, and how many we should have. Let’s say we know that the first argument is a string, the following two are numbers, and the rest are strings. From TypeScript 3.0 we can define it using tuple types as shown in Listing 5.
Listing 5 Implicitly defined types for each argument in an arguments array
How about a different use case? Let’s see Listing 6.
Listing 6 Function for composing two given functions and its usage
TypeScript 3.0 allows us to do a generic type “T1 extends any” which in fact creates a tuple type. Here we tell the compiler that T1 is the type of all of the first function’s arguments. It can be anything, but we will have it strongly typed. As you can see in the last line of listing 6, strict type checking works and tells us that we can’t use a string in place of a number. This example shows that we will be able to use such typings in functional programming. Maybe we will get better typings for libraries like Ramda in the future?
Listing 7 Differences between any and unknown
It’s worth noting that this is a breaking change, because from version 3.0 “unknown” has become a language’s keyword, so it can’t be used as a name anymore.
Support for defaultProps
As we can see, thanks to the usage of defaultProps we don’t need to perform null checks on title, because we are sure that it always has some value. In the past, TypeScript couldn’t understand this and we had to do a null check. In Listings 9 and 10 I’ve shown how we could approach this issue in TypeScript 2.x, and how we can do it now.
Listing 9 TypeScript 2.x way of using defaultProps
Listing 10 TypeScript 3.0 way of using defaultProps
As you can see, the code from the newest TypeScript version doesn’t need the null check, because the compiler can see the parameter value defined in defaultProps.
Probably the biggest new feature of TypeScript 3.0. From now on, we will be able to define cross‑project references. Thanks to this change, we will be able to use some new project architecture scenarios like:
- Using shared code for client and server projects. Compiled output would also share the code, not have a separate copy of each shared code file.
- Unit tests which don’t contain own copy of the main project’s files.
- Monorepos (many projects dependent on each other), along with the use of battle-tested solutions like Lerna and Yarn Workspaces
That’s all thanks to new entries in tsconfig: “composite” flag for compiler options and “references.” Also, we have a new parameter for tsc (TypeScript’s compiler): --build which can build whole TypeScript projects in accordance with a given tsconfig.
As an example, let’s consider the following project structure:
The client’s project tsconfig.json is provided in Listing 11. It can be built using the command: “tsc -b composite/client”. To use a shared project we simply imported the things we need from it without any additional structures in the code.
Listing 11 tsconfig.json with composite flag and reference on shared project
After compiling both client and server we get the following structure:
As you can see, the “shared” directory is in fact shared between the client and the server. In older versions, we would get the “shared” directory copied to both client and server, thus creating an output structure different from the source structure.
For information on how to use project references with Lerna, I can recommend this GitHub repository.
Do you need more technical insights? Check out my other articles!
This post was also published on ITNEXT.