Type Inferences and Annotations in TypeScript
TypeScript bases types detection and definition on Type Inferences and Type Annotations, and this article will explain both of them so you can learn when and how to use them.
Discover how at OpenReplay.com.
One of the distinct features of TypeScript is the fact that it is a statically typed language. This means variables must be assigned to their respective data types when declared because it is checked at compile time. This is different from the syntax of JavaScript, a dynamically typed language, which allows you loosely declare variables without having to specify the data type — the interpreter assigns variables to their types at run time. The most important feature of using statically typed languages for developers is that it performs error checking at compile time; this increases speed in development time because you can immediately see if there is a type mismatch in your code or if you are trying to call a function that does not exist. This is a better method in comparison to dynamically typed languages that perform error checking at run time.
In TypeScript, variables must be assigned to types to avoid unwanted results. There are two major ways of type representation in TypeScript; Type Inference and Type Annotations. In this article, you will learn about type annotations and type inference, how to use them, and the best practice to use. Let’s dive in.
Type Inference
One of the ways TypeScript represents types is by Inference. In this section, you will learn about type inference, how TypeScript infers types, and how to use them.
What is Type Inference?
Sometimes, we may not assign types, and the TypeScript compiler does that for us by Type Inference. It is the process where TypeScript automatically infers types to variables with the help of the TypeScript Compiler. Consider the line of code below:
let x = "OpenReplay is an open-source session replay suite";
Although we have not explicitly defined our variable as a string, the TypeScript compiler automatically infers the variable x
as a string. To confirm this, let’s consider the line of code below:
var x = "OpenReplay is an open-source session replay suite";
var y = 17;
x = y; // Compile-time Error: Type 'number' is not assignable to type 'string'
In the above code snippet, we define a variable x
without assigning it to a type(remember the TypeScript Compiler does that for us, so it is seen as a string). We also define a variable y
and assign a number 17
without an explicit type annotation stating it is a number. Why? Because we want the TypeScript Compiler to do that for us by Inference.
Next, to confirm that TypeScript sees x
as a string and y
as a number, we assign the variable y
to the variable x
. It throws an error at compile time saying “Type 'number' is not assignable to type 'string'
”. This proves that TypeScript has automatically inferred the variable x
as a string and variable y
as a number.
How does TypeScript Infer Types?
Suppose we do not assign our variables to types; how does TypeScript know what data type intend to use? The moment we initialize our variables and values, the TypeScript Compiler scans the syntax and automatically infers the type. If the value is wrapped in quotes, it is inferred as a string; if the value is a numerical value, it is referred to as a number if it is either true
or false
, it is inferred as a boolean value, and so on.
If we have an array of different data types, TypeScript infers types using the Best Common Type Algorithm. Using the best common type algorithm, the TypeScript compiler picks the data type that works for all the values in the array. Consider the block of code below:
let x = [5, 6, null]
Here, multiple values are assigned to x
; TypeScript looks at all the values and picks the best common type representing all the values in the array. For the above code snippet, we have two types, a number type, and a null type. The first two values, 5
and 6
, are represented using the number type, while null
is represented using the null type. Therefore, the TypeScript compiler represents the data type of our variable x
as let x: (number | null)[]
.
Practical Example of Type Inference in TypeScript
Let’s look at what goes on behind the scenes in the TypeScript compiler when we define variables without assigning them to types. Consider the block of code below:
// Numbers
let rating = 5;
let age = 63;
// Strings
let blog = "OpenReplay";
let feature = "Host your session replay tool yourself with OpenReplay";
// Booleans
let x = true;
let y = false;
// Object of strings
const company = { companyName: "OpenReplay", location: "San Francisco" };
In the above code snippet, I have defined variables that TypeScript should automatically represent as numbers, strings, booleans, and objects. You can run the code in your editor; I will use the online TypeScript Playground.
When it is run, and I click on the module .d.ts
bar, this is the output:
On the right-hand side, we see that even though we did not assign the types, the TypeScript compiler does that for us. It recognizes the number type, string type, and boolean type.
Type Annotations in TypeScript
In this section, you will learn about type annotation, how to declare types using type annotations, and data types in type annotation.
A Type Annotation is a way to represent data types in TypeScript. It follows a variableName:type
syntax. Unlike type inference, you have to explicitly assign variables to their types. This might seem as much work, but it is used to enforce strict type checking. It also saves time because a type mismatch error normally thrown at run time is thrown at compile time using type annotations. Type annotation is a good coding practice among developers because it saves time by detecting bugs early and ensures good code readability. Let’s see how this works. Consider the block of code below:
let x: string = "OpenReplay is an open-source session replay suite";
We see that in this block of code, we specify that our variable x
is a string. If we try to assign x
to a variable of another data type, it throws an error at compile time.
Declaring Types using Annotations
In Type annotation, we assign types for every variable we declare in our codebase. Let’s take a look at a sample code:
// Numbers
let rating: number = 5;
let age: number = 63;
// Strings
let blog: string = "OpenReplay";
let feature: string = "Host your session replay tool yourself with OpenReplay";
// Booleans
let x: boolean = true;
let y: boolean = false;
// Object of strings
const company: { companyName: string; location: string } = {
companyName: "OpenReplay",
location: "San Francisco",
};
Rather than allow the TypeScript compiler to automatically assign our types, we do it ourselves. This is a very good practice in ensuring code safety because there is a low possibility of type mismatch.
Data Types in Type Annotations
In addition to assigning types to variables using the primitive data types in JavaScript, we can also assign types to functions, object types, and union types.
- To assign types to functions, we add the data type as a parameter; this specifies the type of parameters the function accepts.
function firstName(name: string) {
console.log("My name is " + name.toUpperCase());
}
- To assign types to objects, we annotate the variable names before we pass in our values. In the code snippet below, we specify that the
companyName
andlocation
will be a string just before we pass in the values.
const company: { companyName: string; location: string } = {
companyName: "OpenReplay",
location: "San Francisco",
};
- To assign types to a union(a combination of types), we specify the data types we want to use in the function, separated by a vertical bar.
function blog(id: number | string) {
console.log("OpenReplay is rated " + "id" + "star");
}
// OK
blog(5);
// Error
blog(true);
Here, we specify that the variables can be the number or string types. The blog(true)
argument throws an error because it is a boolean type not represented in the union.
Best Practice — Type Inference or Type Annotations?
Choosing between type inference and type annotations depends on the project’s scope. However, using type annotations in assigning types to variables in TypeScript is very good practice. The usage of type annotations is better for the following reasons:
- Faster development time since it detects errors at compile time.
- It is easier to read code using annotations. This is important when working in teams.
- There is a very low possibility of type mismatch using type annotations.
Conclusion
In this article, you have learned about type inference and type annotation in TypeScript, how TypeScript infers types, how to declare types using annotation, the data types in type annotation, and the best practice for your next TypeScript project. Have some fun with it!
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.