Photo by Amar Yashlaha on Unsplash
Last year I wrote a post like this about Angular, and I think it was really helpful for new corners. I want to do the same for new people reaching Typescript.
What is Typescript?
Typescript is an open-source programming language developed and maintained by Microsoft. It's a strict superset of JavaScript, adding optional static typing to it. It was first launched on October 1st, 2012, almost 7 years ago, and now is version 3.7, after many releases. Because Typescript doesn't follow the semantic version, each version introduces both new features and a few breaking changes. Until this year the release cycle was one new version every two months, but they have moved to a 3 months release cycle to improve the stability of each release. It is developed with TS and compiled to JS using the TSC, meaning it compiles itself.
In early versions, Typescript introduced concepts that were not stable into JS, such as classes, modules, and others. These concepts lead to the misbelief that Microsoft was trying to create a new scripting language to replace JavaScript, rather than a language to improve JavaScript itself. In newer versions, Typescript has adopted the new features of newer versions of the ECMAScript spec and has improved the integration with plain JavaScript files as well.
What means for Typescript to be a superset of JavaScript?
In plain words, every JavaScript source file should just work. But, that's not always the case. In order for this to be true, you should disable Typescript strict
type checking, that's enabled by default since its introduction in version 2.3. But, then you won't be leveraging of the type checking as you should. If you try to compile any JS source file with the Typescript compiler just by changing the extension from .js
to .ts
you probably will find some complaints from the tsc
.
Introducing Typescript Compiler (tsc
) and type annotations
We are going to see an example. First, we are going to install typescript
. You can install it globally, but for this example, I'm going to create an npm project. Make sure you are using the latest Node LTS.
$ mkdir ts-example
$ npm init -y
$ npm i typescript
$ touch fibonacci.ts
We paste the code:
function fibonacci(num, memo) {
memo = memo || {};
if (memo[num]) return memo[num];
if (num <= 1) return 1;
return (memo[num] = fibonacci(num - 1, memo) + fibonacci(num - 2, memo));
}
$ npx tsc
See those errors in the console:
fibonacci.ts:1:10 - error TS7023: 'fibonacci' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
~~~~~~~~~
fibonacci.ts:1:20 - error TS7006: Parameter 'num' implicitly has an 'any' type.
1 function fibonacci(num, memo) {
~~~
fibonacci.ts:1:25 - error TS7006: Parameter 'memo' implicitly has an 'any' type.
1 function fibonacci(num, memo) {
~~~~
- Or, if you are using an editor:
function fibonacci(num, memo) {
~~~~~~1 ~~2 ~~3
memo = memo || {};
if (memo[num]) return memo[num];
if (num <= 1) return 1;
return (memo[num] = fibonacci(num - 1, memo) + fibonacci(num - 2, memo));
}
This is a valid JS function. A Fibonacci function that storages the result of previous calls in memo object
and then return again in a recursive calling, until it reaches the end.1 Though Typescript reports 3 errors, all of them are likely the same, but we are going to check them individually:
'fibonacci' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
Parameter 'num' implicitly has an 'any' type.
Parameter 'memo' implicitly has an 'any' type.
What does that mean? You may ask. Well, that it's Typescript telling you that it's unable to do what it supposed to do, type. It doesn't have information about what fibonacci
return type is, neither what should the parameters be. But, we can fix that, because we know this information and we can provide TS with it.
-
fibonacci
function should return a number. -
num
parameter should be a number. -
memo
parameter should be an object.
Now we use the Typescript type annotations to provide that information that to the tsc
. Type annotations in Typescript are descriptions that start after the declaration of the variable with a colon followed by a type. For functions and methods, that description is for the return type rather than for the function itself.
var a: number;
// "var a" is the regular JS variable declaration, and ": number" is the type annotation.
function a(): string {/* */}
// "function a() {}" is the regular JS function declaration, and ": string" is the return type annotation.
So, we use this for our Fibonacci example and run the tsc
command again, or if we are in an editor:
function fibonacci(num: number, memo: object): number {
memo = memo || {};
if (memo[num]) return memo[num];
~~~1 ~~~2
if (num <= 1) return 1;
return (memo[num] = fibonacci(num - 1, memo) + fibonacci(num - 2, memo));
~~~3
}
Now we have a second error. And this is often the point when people start complaining.
- You know what? F*ck it! It's not my fault Typescript doesn't understand this simple code. Well... Maybe, but do you understand better the function with or without the annotations? With the type annotation, you can read directly the type you need, and how to use it.
-
I can annotate my code with JSDocs. Yes, you could, but there is nothing that would check your code, but maybe the Typescript compiler itself, more on that later, and if you already going to use the
tsc
why don't writets
files?
Introducing interfaces, types, and type aliases
Back the errors, we can notice that it's actually the same error: Element implicitly has an 'any' type because the expression of type 'number' can't be used to index type '{}'. No index signature with a parameter of type 'number' was found on type '{}'.
That's maybe not the best description of the problem but is something you may read a lot. tsc
is complaining about memo
having the object
type. In strict mode
, object
type is equal to an empty object ({}
), and accessing a property that is not declared in the variable type would mark that property as any, because Typescript doesn't know it's typing, but it could assume it does exist somehow, but not in strict mode
. Since we are in strict mode
Typescript is telling us: Hey, you are trying to access a property that I don't know of, and I could give it to you like any, but you don't want that either. So, I need you to give the right typing information about this property you are trying to get.
We are going to solve this using type aliases. Typescript has 3 ways of getting type information:
A JS
class
has its own type information, and also is a valid constructor function usable though the application. You can both extend, and implement classes in other classes, notice you can implement many classes, but only can extend one.An
interface
. This is a TS feature that allows you to declare the shape of an object. This doesn't exist in runtime, so you can't invoke it or assign it as aclass
, don't worry, TS will complain if you try to do something like that. You can extend both classes and other interfaces in an interface declaration. You can implement many interfaces in aclass
.A
type
. This is another TS feature that allows you to declare the shape of an object. This also doesn't exist in runtime, but you can't extend it and only can implement it if thetype
has statically known members. With atype
you can use instead type modifiers, mappers, conditionals, and other complex typing structures, like type aliases.
So, a type alias is a type
that helps us to express better the shape of a complex object. How complex is memo
? You may ask. It's actually kind of complex to express an object when you don't know all the properties it would have. The properties of memo
are calculated in runtime, they are not statically known. Typescript has several built-in official helpers, but for some reason they are not listed in the official documentation. Some of those aliases are: NonNullable
, ReturnType
, InstanceType
, Readonly
and Record
. The last one is the one we are going to use to fix our problem:
function fibonacci(num: number, memo: Record<number, number>): number {
memo = memo || {};
if (memo[num]) return memo[num];
if (num <= 1) return 1;
return (memo[num] = fibonacci(num - 1, memo) + fibonacci(num - 2, memo));
}
With Record<number, number>
we are telling Typescript that memo
is an object with number
indexes and number
properties. It checks it, and everything seems to be ok now.
Compiling
We know the tsc
command (or not) but what it does it just compile (technically transpile) from Typescript to vanilla JavaScript while checking all that all the types are correct. By default, tsc
will output the .js
file regardless the types are correct or not. If you look at the output it looks more like the first version of our example, and that's ok. That's because no device can run TS source code, but JS, so what TS does is removing all our annotations and leaving a nice and clean JS file.
The power of the tsc
is in its configuration file, tsconfig.json
and its Compiler Options. With that you can enable or disable powerful features, such no emits on errors or allow JS files to be compiled.
Looking forward
As I said earlier, TS moves really fast. They recently introduced new ES features, as Optional Chaining and Nullish Coalescing, and also new TS features as Recursive Type References and Assertion Signatures, and some new features looking to be introduced in TS 3.8 in February. Because of the breaking changes, frameworks that use TS (as Angular or React) can't go that fast, so they often offer support for a new version of TS a couple of weeks after it was released. But this is changing as TS now provides an RC version with a stable API. However, it doesn't stop you to play around with the new features in the Playground.
Bonus: Typing JavaScript
You can use JSDocs to give type annotations to variables and methods in JS files. Then, you can use a TS powered editor like VS Code to do all the checking and the power of TS in regular JS files.
[1]: For the record, I got that function from here: https://medium.com/developers-writing/fibonacci-sequence-algorithm-in-javascript-b253dc7e320e. I ask no permission, and I hope for the author not to be mad about it. Thank you for sharing this. 😄. I took it because Fibonacci functions look easy, but they are complex to type, as we had seen.