The introduction to Typescript you may be missing

Michael De Abreu - Sep 17 '19 - - Dev Community

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.

  1. $ mkdir ts-example

  2. $ npm init -y

  3. $ npm i typescript

  4. $ touch fibonacci.ts

  5. 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));
   }
Enter fullscreen mode Exit fullscreen mode
  1. $ npx tsc

  2. 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) {
                             ~~~~
Enter fullscreen mode Exit fullscreen mode
  1. 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));
   }
Enter fullscreen mode Exit fullscreen mode

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:

  1. '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.
  2. Parameter 'num' implicitly has an 'any' type.
  3. 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.

  1. fibonacci function should return a number.
  2. num parameter should be a number.
  3. 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.
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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 write ts 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 a class, 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 a class.

  • 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 the type has statically known members. With a type 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));
}
Enter fullscreen mode Exit fullscreen mode

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .