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;
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;
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;
a
automatically will have type of number. The declaration with annotation will legit as well.
let a: number = 42;
However, we should avoid this way. The following example straightforwardly illustrates the benefit of inference.
const a: string = 'Kirk';
The type of a
is string.
If we use inference:
const a = 'Kirk';
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);
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)