What & why ? 🧐
TypeScript is an open-source language which builds on JavaScript. At the end of the day, TypeScript is no more than compiled JavaScript code and it runs everywhere.
Writing TypeScript code let you catch error during development and saves you a lot of time when debugging. It brings new features that considerably improve your code maintainability and if you are already familiar with JavaScript, there is a gentle learning curve.
Setup 🔨
In order to get started with TypeScript, you should install the compiler locally.
npm install -g typescript
You can now create any .ts file you want and run the following command:
tsc app.ts
It should compile your app.ts file and create a new app.js file that contains "classic" JavaScript code.
TypeScript compiler is very flexible and lets you decide how it should behave. The first thing you have to do when starting a new project is to run this command:
tsc --init
Well done, you successfully created a tsconfig.json file that allows you to customize the compiler behavior.
Understanding the configuration file is a great way to improve your TypeScript skills, read the official doc if you want to dive deeper in.
Core features ⭐️
✅ Types
The main feature that comes from using TypeScript is type declaration. Most of JavaScript code running out there is un-typed and it is perfectly fine but if you are used to strongly typed programming languages, you may not like it so much.
TypeScript comes to the rescue, you are now able to explicitly tell the compiler what type of data your variables should have and what type of data a function returns.
// app.js
let num;
num = 5;
num = "Five"; /* It works */
// app.ts
let num: Number;
num = 5;
num = "Five"; /* Type 'string' is not assignable to type 'Number' */
In this example, the compiler will complain if I accidentally assign a String to a variable that was supposed to be a Number.
Here is a non-exhaustive list of types that TypeScript understands:
// Primitives
const num: Number = 5;
const word: String = "Hello";
const bool: Boolean = true;
// Non-Primitives
const numArr: Array<Number> = [1, 2, 3];
const obj: Object = {};
// Tuples
const tuple: [String, Number, Number] = ["Hello", 1, 2]; // Fixed size and types array
// Unions
let union: Number | String = 5; // Allow multiples type possibilities
union = "World";
// Enums
enum Color { // Initialize numeric values with a name
Red = 0,
Blue = 1,
Green = 42,
}
const color = Color.Green;
console.log(color); // Displays 42
// Any
let something: any; // Any type
// Void
let nothing: void;
const returnNothing = (): void => console.log("Nothing"); // Return nothing
// Never
const error = (message: string): never => { // Always throw an exception
throw new Error(message);
}
// Custom
type Name = "John" | "James";
let customName: Name;
customName = "John";
customName = "James";
customName = "Max"; // Type '"Max"' is not assignable to type 'name'
✅ Object-Oriented Programming
JavaScript already supports the object-oriented approach but with TypeScript, we take things to the next level!
If you are unfamiliar with classes, here is an example:
// Class
class Pet {
name: String;
constructor(name: String) {
this.name = name;
}
makeNoise = (): void => console.log(`${this.name} makes noise`);
}
// Inheritance
class Dog extends Pet {
breed: String;
constructor(name: String, breed: String) {
super(name);
this.breed = breed;
}
}
const dog = new Dog("Max", "Akita");
dog.makeNoise(); // Displays: Max makes noise
So far, nothing really new. This works quite the same way with JavaScript.
Access modifiers are a thing in many others programming languages and thanks to TypeScript we can work with them as well.
// Class
class Pet {
public name: String; // Accessible everywhere
private _age: Number; // Accessible from the class itself only
protected isDog: Boolean; // Accessible from the class and its subclasses
static petCount = 0; // Not accessible from instances of the class
static readonly species = 'Canis Familaris'; // Cannot be modified
constructor(name: String, age: Number, isDog: Boolean) {
this.name = name;
this._age = age;
this.isDog = isDog;
}
makeNoise = (): void => console.log(`${this.name} makes noise`);
}
const pet = new Pet("Maw", 5, true);
console.log(pet.name); // Displays: "Max"
pet.name = "Rex";
console.log(pet.name); // Displays: "Rex"
console.log(pet._age); // Property 'age' is private and only accessible within class 'Pet'
console.log(pet.isDog); // Property 'isDog' is protected and only accessible within class 'Pet' and its subclasses.
console.log(Pet.petCount); // Displays: 0
console.log(Pet.species); // Displays: 'Canis Familaris'
In this dummy example, we manage the access to our class variables thanks to access modifiers. They prevent unwanted variables assignments from the outside.
A quick side note for the 'static' keyword. Static variables are available on the class itself and does not differ from one instance of this class to another.
Read more about static variables here.
Finally, let me introduce interfaces! Interfaces describe a set of attributes that an object should implement.
interface iShape {
draw: Function;
width: number;
height: number;
}
class Square implements iShape {
width: number;
height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
draw = () => console.log(`Drawing shape ${this.width * this.height}`);
}
class Circle implements iShape {}
// Class 'Circle' incorrectly implements interface 'iShape'.Type 'Circle' is missing the following properties from type 'iShape': draw, width, height
Here is a potential use case, we want to make sure that our 'shape' variable has a width, height and a draw method:
interface iShape {
draw: Function;
width: number;
height: number;
}
class Square implements iShape {
width: number;
height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
draw = () => console.log(`Drawing shape ${this.width * this.height}`);
}
class UIElement {
width: number;
height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
}
const square = new Square(50, 50);
const image = new UIElement(100, 50);
let shape: iShape = square;
shape = image;
// Property 'draw' is missing in type 'UIElement' but required in type 'iShape'.
✅ Generics
Getting started with typed programming, you could get confused and write code like that:
const numFun = (num: number): number => {
return num;
}
const strFun = (str: string): string => {
return str;
}
Of course, this would lead to a huge amount of work ...
One possible workaround is to work with the "Any" type.
const anyFun = (arg: any): any => {
return arg;
}
This works just fine, but we are losing the whole interest of working with TypeScript: type safety.
Once again, TypeScript comes with a built-in solution to improve code reusability.
const generic = <T>(arg: T): T => arg;
Conclusion
TypeScript is a great tool to work with and has a lot to offer. I am only at the beginning of my journey and I already love it.
Let me know what you think of TypeScript and my article.
Thank you for reading! 😇
Top comments (0)