How to Get Started With TypeScript
JavaScript is weakly typed by its nature. This means that data types are always inferred from the variable declarations. This makes the language really flexible, and easy to learn for new programmers who don’t want to worry about different data types. But there’s a downside to this flexibility.
This can cause unexpected errors in your applications that you can easily catch with TypeScript even in your IDE before compile time. Imagine you are working on a game and you have some scores to be displayed. The data is stored in a database on the server, which you request. You get it back as a number and based on the values of other DOM elements, you want to increment this number. So you get the innerText
from the DOM element and add it to your score:
const score = 100;
const bonus = document.getElementById('bonus').innerText; // Returns "20" as a string
// Score will be "10020"
score += bonus;
This isn’t quite the behavior you were looking for, but no errors will be thrown as JavaScript silently converts the number into a string. This could have been easily avoided.
TypeScript — a superset of JavaScript — can turn your JavaScript files into strongly typed TypeScript files. And since any valid JavaScript file also works as a TypeScript file, the learning curve is really shallow. You can type your already existing JavaScript code, incrementally as you go, without having to worry about breaking anything.
Get Started With TypeScript
To get started with TypeScript, you can install it globally by running npm i -g typescript
in your terminal. With that installed, you should be able to transpile TypeScript files, using the tsc
command in your terminal. Create a new file called index.ts
, and add the following line, then run tsc index.js
in the directory where the file has been created:
const hello = '👋';
You will see that TypeScript will generate an index.js
file next to your TypeScript file with the following content:
var hello = '👋';
It changed the const
keyword to var
, so we can be sure that TypeScript is now set up and is working. This happens because it transpiles your code down to ES3 by default. Of course, this code has nothing to do with TypeScript so far, the const
keyword is part of ES6. It just shows that TypeScript can also transpile your code down to a different version of JavaScript. You can target the version among other things with CLI flags. For example, to transpile to ES6, you would do:
tsc index.ts -t ES6
tsc index.ts -target ES6
Both of them will work, and you get back the same variable with the const
keyword. Of course, it would be super tedious to always write out these flags into the terminal, so TypeScript provides a configuration file, where you can configure every aspect of the compiler.
Configuring TypeScript
For that, you’re going to need to have a tsconfig.json
file at your project’s root. Here you can specify a compilerOptions
node where you can set the target
among many other things.
{
"compilerOptions": {
"target": "ESNext"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
Here you also have the option to include or exclude certain files from transpiling with glob pattern support. For the full list of available compiler options, you can refer to the official docs.
Using TypeScript
Now that you have everything set up, let’s start using TypeScript. First, let’s see how you can define types for your variables. By default, variable types are inferred from the declarations. For example, if you have the following variable:
const number = 5;
typeof number // returns "number"
const number = '5';
typeof number // returns "string"
It will be inferred as a number. JavaScript automatically assigns a type to it explicitly. If you change it to a string, then the type of the variable will be a string as well. In order to ensure we are dealing with a number, we need to assign a type using a double colon, followed by a type:
const number: number = 5;
If you transpile your file into JavaScript, you will notice you won’t get any additional code in your output. You will only have var number = 5;
. TypeScript checks the types at compile time. If you change the value of the variable to a string now, you will get an error, and you won’t even be able to transpile your ts
file.
Apart from the number type, you have many other types you can use. Some of the more commonly used that you need to be aware of are:
Tuples
You know most of these already from JavaScript, but there are a couple of other types we need to talk about. For example, a tuple is a fixed size array where you know the type of each element:
const tuple: [string, number] = ['0', 1];
This way you won’t be able to add or remove elements from the array. Also if you are referencing one of the elements, you get the proper object methods. For example on tuple[0]
you will get an error if you try to use toFixed
as it is not a string, but a number. And when you are dealing with regular arrays, you have two ways to type them:
// Use the type of the element followed by []
const emojis: string[] = ['1️⃣', '2️⃣', '3️⃣'];
// Use the generic array type: Array<type>
const emojis: Array<string> = ['1️⃣', '2️⃣', '3️⃣'];
Enums
Enums are another helpful addition to JavaScript. With enums, you can create a set of values with more meaningful names. Take the following as an example:
enum Direction {
Up,
Left,
Down,
Right
}
Here, each value is a numeric value, starting from 0. This means that Right
will be equal to 3. If you transpile this down and look into the JavaScript file, you will see the following generated:
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Left"] = 1] = "Left";
Direction[Direction["Down"] = 2] = "Down";
Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));
You can access the values just like you would for any other object in JavaScript.
Direction.Up
Direction.Down
... and so on ...
You can also change the starting value by assigning a number to it, or if you prefer, you can assign a value for each of them to further improve readability.
// Starting from 1 instead of 0
enum Direction {
Up = 1,
Left,
Down,
Right
}
// Asigning a value to each of them for better clarity
enum Direction {
Up = 1,
Left = 2,
Down = 3,
Right = 4
}
// You can also use strings as values
enum Direction {
Up = 'UP',
Left = 'LEFT',
Down = 'DOWN',
Right = 'RIGHT'
}
Any
One of the best features of TypeScript is the any type. The any type means that your type can be anything. You either don’t know it upfront or you don’t care. You would use this type when you are dealing with 3rd party code, or you are using an API, where you don’t know the return type. It’s also comes really handy when you want to migrate a big project from JavaScript to TypeScript. You can opt-out of type checking certain variables if you need to deliver code changes in a strict time schedule.
let variable: any = "I'm a string";
variable = 5 // Maybe I'm a number
variable = false; // Or am I a boolean?
Custom Types
Apart from the built-in types of TypeScript, you can also define your own types. So far, we’ve seen how to make a variable typed. But what if a variable can take up multiple types? For this, you can use a pipe (|
):
const easing: 'LINEAR' | 'EASE-IN' | number = 'EASE-OUT';
The above example would throw an error as the type can be either a string (one of LINEAR
or EASE-IN
) or a number. To outsource this type and reuse it elsewhere, you can use the type
keyword to define it as a custom type:
type Easing = 'LINEAR' | 'EASE-IN' | 'EASE-OUT' | number;
const easing: Easing = 'EASE-OUT';
Now this won’t throw any errors as we can also use EASE-OUT
. This especially comes useful when you are dealing with more complex objects:
type HealthBar = {
max: number,
segment: number
}
type Enemy = {
health: HealthBar,
name: string,
damage: number,
alive: boolean
}
const enemy: Enemy = { ... }
Note that you can also nest different types into each other. And if you incorrectly define your variable, you also get helpful error messages on what went wrong, and how you should fix it.
But what if you want to omit some of the properties for other types of enemies? Luckily, you don’t need to create entirely new types, instead you can use optional properties with a question mark before the double colon:
type Enemy = {
health: HealthBar,
name: string,
damage: number,
alive: boolean,
boss?: Boss
}
Here the boss
property is optional, meaning even if you don’t pass it to your objects, TypeScript will still compile.
Functions
Functions are a fundamental building block in JavaScript. We’ve talked about a couple of basic types so far from the list above, but we haven’t touched the void type yet. When you are dealing with functions, you will most probably see void
a lot. The type void
infers that a function has no return value. Take the following as an example:
const addClickEventListener = (e: Event): void => { ... }
Here you are specifying the type of the return value of the function, after the parentheses. Since there is no return value for an event listener, the void
type is used. Just like we did previously with variables, you can also type the parameters.
const add = (a: number, b: number): number => a + b;
Here the function returns a number
, and both of the parameters are also typed as numbers
.
Interfaces
Another common data type you’re going to find in TypeScript is interfaces. Very similar to the type
alias, but unlike them, you can’t use interfaces for primitives, only for objects.
// ✅ this works
type Easing = 'LINEAR' | 'EASE-IN' | 'EASE-OUT' | number;
// ❌ this doesn't
interface Easing = 'LINEAR' | 'EASE-IN' | 'EASE-OUT' | number;
// ✅ this is valid
interface Easing {
type: 'LINEAR' | 'EASE-IN' | 'EASE-OUT' | number;
}
Also, interfaces are always extensible, and multiple interfaces with the same name will be merged together. This is not something you can do with type aliases.
interface Easing {
type: 'LINEAR' | 'EASE-IN' | 'EASE-OUT' | number;
}
interface Easing {
timing: number
}
These two interfaces will be merged together into one that has both a type
and a timing
property. You cannot do this with types.
What you can do instead, is use an ampersand to extend a base type in the following way:
type Easing = {
type: 'LINEAR' | 'EASE-IN' | 'EASE-OUT' | number;
}
type EasingWithTiming = Easing & {
timing: number
}
// The same can be done with interfaces using the `extends` keyword
interface EasingWithTiming extends Easing {
timing: number
}
If you go ahead and transpile your file now to JavaScript, you will notice that nothing is generated for interfaces or type aliases, the compiler only uses this to check validity at compile-time, so it won’t affect your performance and bundle size in any way.
Classes
Classes are another extension in TypeScript. ES6 also introduced the class
keyword, but for example, it lacks modifiers such as the public
, private
or protected
keywords.
class Widget {
private isCollapsed: boolean;
constructor(collapsedByDefault: boolean) {
this.isCollapsed = collapsedByDefault;
}
getState(): boolean {
return this.isCollapsed;
}
}
You may notice we have only defined a private
modifier, but nothing for the constructor
and the getState
method. In TypeScript, each member is public
by default, so you can omit them. Again, if we transpile this code, we can see what TypeScript generates for us. In this case, all the way down to ES3.
var Widget = /** @class */ (function () {
function Widget(collapsedByDefault) {
this.isCollapsed = collapsedByDefault;
}
Widget.prototype.getState = function () {
return this.isCollapsed;
};
return Widget;
}());
Generics
Lastly, I wanted to talk a bit about generics. When building applications, you often have to think about how to create reusable components, that also have a well-defined API. These components should work on a variety of types, instead of only handling just a single one. That makes them so flexible. You might be thinking the simplest way to create a generic is by using the any
type.
const fn = (param: any): any => param;
And while that is true, this function will accept any type as an argument and can return anything, in the meantime, you lose information about what type is passed and what type is returned. You can know that a number is passed to the function, but you can’t be sure that a number is returned as well. It can be anything. Instead, what you can do is use a type variable to identify what type is being passed to the function, and what type is returned:
function fn<T>(param: T): T {
return param;
};
Here, T
identifies the type. You can then call this function in two ways with a specific type:
fn<number>(10); // Passing the type argument
fn(10); // let TypeScript automatically set the type of `T` based on the argument
Conclusion
And now you know everything you need to know to get started with TypeScript. If you find yourself stuck while using it, I highly recommend checking out their official documentation, it should have all the necessary information you need.
Have you worked with TypeScript before? Let us know your thoughts about it in the comments down below! Thank you for reading through, happy coding!
Rocket Launch Your Career
Speed up your learning progress with our mentorship program. Join as a mentee to unlock the full potential of Webtips and get a personalized learning experience by experts to master the following frontend technologies: