DEV Community

Cover image for Types in Typescript plainly
Arthur Groupp
Arthur Groupp

Posted on • Edited on

Types in Typescript plainly

From the beginning, Typescript was presented as JavaScript improvement with addition of types. But why? More restrictions make less flexibility. We want to do whatever we want. For example, in JavaScript I can do the following:

let a = 42;
a = 'bunny';
let b = a / 2;
Enter fullscreen mode Exit fullscreen mode

This code is legit in JavaScript. However, what will happen during runtime? b will get the value of NaN, not a number. And, if somewhere later on in our program we'll use b it can bring us to runtime error. In Typescript this code will not compile because we can't assign string value to variable, that is number. As a result, it will save us much debugging time and struggle with bugs.

Strict types allow preventing many runtime errors during development stage. Typescript allows usage of scalar and compound types. All scalar types are derived from JavaScript and equivalent to them. Compound types are extension of JavaScript object. By that, it shows the issues at compilation moment instead of runtime.

Compound types

Typescript allows describing the shape of application data by classes, interfaces and types. Classes are regular JavaScript classes and OOP is out of scope of this article. Additionally, Typescript suggests us to use interfaces and types. The key difference with classes is that interfaces and types are deleted by compiler. This limits their usage. For example, we can use keyword new with classes only. When we need to decide if we need to use a class or interface, we need to answer the question "Do I need to create an instance of this type?" The difference between interface and type is that we cannot extend type, but we can combine it with other types using logical operators (&, |). Classes can implement multiple interfaces and this is the only way of multiple inheritance in Typescript.

Annotation

By default, just declared variable has type any, that is self-explanatory. It means that we can assign value of any type to this variable. And it is definitely unacceptable in our world of law and order. To make variable strictly typed, we need to annotate it. Annotation is telling the compiler which type of data we can assign to this variable.

let a: number;
let b: boolean;
Enter fullscreen mode Exit fullscreen mode

If we won't annotate the variable, it will have type any. In strict mode of the compiler (that must be default in all of our projects), we will get error about it.

Inference

Typescript has a type inference engine built in. It means that it can automatically detect the type of expression. To achieve benefits of this technology, we must initialize variables during declaration.

let a = 42;
Enter fullscreen mode Exit fullscreen mode

a automatically will have type of number. The declaration with annotation will legit as well.

let a: number = 42;
Enter fullscreen mode Exit fullscreen mode

However, we should avoid this way. The following example straightforwardly illustrates the benefit of inference.

const a: string = 'Kirk';
Enter fullscreen mode Exit fullscreen mode

The type of a is string.
If we use inference:

const a = 'Kirk';
Enter fullscreen mode Exit fullscreen mode

the type of a is "Kirk", that makes a much more precise.

Let's look at the following example:

type Names = 'Jim' | 'Spock' | 'Leonard';

function processName(name: Names) {}

const name1: string = 'Jim';
const name2 = 'Jim';

processName(name1);

processName(name2);
Enter fullscreen mode Exit fullscreen mode

Calling function processName with name1 won't compile because argument of type 'string' is not assignable to parameter of type 'Names'.

There are cases when inference doesn’t work well. For example, if we initialize our variable by array of objects, that are instances of classes that extend one base class, we will have array of common type that will be combination of those children's classes. In this case we will want to manually annotate the variable as array of base class.

Sometimes the compiler gets confused. Usually, we should consider it as a gap in types architecture and probably revise it.

Functions

Highly important to specify functions returning types. Of course, Typescript can infer it from return statement. However, when we start to construct our function and annotate it, IDE will help us to return the correct type.

RxJS

Starting with version 7 of RxJS the typing was highly improved. And this is a good reason for upgrade of your projects. There is almost no need any more to annotate projection functions arguments, even after filter.

Conclusion

Understanding and usage of types is cornerstone of modern front-end development. We should always use inference wherever it's possible. We should always specify the return type of our methods and functions. And we should almost never use any.

Photo by Judith Frietsch on Unsplash

Top comments (0)