What is the point of using TypeScript ?
JavaScript is a dynamically typed language meaning type checking is performed at runtime or at the time of execution.
JavaScript allows accessing values which aren't present leading to unexpected bugs.
const person = { name: "John Doe", id: 1 };
console.log(person.email) // <- undefined
person.name.toUppercase() // <- will throw TypeError
TypeScript was created to solve these type of problems by adding static type checking to JavaScript.
Think of JavaScript as a person which doesn't provide feedback until you Beat the s- I mean ask it. The feedback provided however is not much in-depth such as where might the error be until you console log it like above.
TypeScript is a typed superset of JavaScript which provides static type checking (type checking is performed at compile time) to catch potential bugs before runtime. It adds type safety to your JavaScript code.
With TypeScript, The above code will now throw an error in the IDE before runtime.
Typescript is basically a more wiser version of JavaScript which helps you catch a lot of errors including typos, uncalled function (Math.random * 6), etc.
TL;DR
types + Javascript = Typescript.
Basic Types
Before using Typescript we need to know about JavaScript types and some special TypeScript types.
Here are a bunch of them -
-
string
-
number
(there's no int or float data type in JavaScript) boolean
- Arrays (datatype[] or
Array<datatype>
) - object ({})
-
any
(NOTE :- This removes the type safety provided by TS, meaning you will be back to the same old JavaScript for that particular variable) - undefined
- void
- never
- unknown
- Union Types (combination of two or more types)
- Enum
and more.
Type Annotations and Inference
TypeScript allows you to optionally specify the type of variable while declaring it.
let name: string = "senbo";
let count: number = 0;
let isEven: boolean = true;
let array: number[] = [1, 2, 3, 4, 5];
let obj: { a : number; b : number; c?: number } = { a : 10, b : 20
let age: number | string = 20; // union type, age is either a number or string
let alignment: "left" | "centre" | "right"; // Union of literal types
};
These are called Type Annotations.
In the example of object, we use ;
separate the properties and declare a property optional by adding ?
after its name.
But this is redundant as TypeScript can automatically infer the type of variable based on assigned value.
This is called Type Inference.
Functions in TypeScript
While declaring a function you can add Type Annotations to parameters and return type, so that arguments to that function gets checked.
function add(a: number, b: number): number {
return a + b;
}
In the above function, a and b are assigned the type number
as well as the return type which comes after the closing parenthesis of parameter list, note that the return type can be inferred by typescript so it is not necessary to write always.
Here are some more examples
function print(name: string): void {
console.log(`Hello ${name}`);
}
function sum(...numbers: number[]): number {
return numbers.reduce((acc, curr) => acc + curr);
}
function printLength(item: string | string[]) {
if (typeof item === "string") {
console.log("Length:", item.length);
} else {
console.log("Lengths:", item.map(str => str.length));
}
}
The first function, print
, simply console logs the string, hence the return type is void.
The second function, sum
, uses the Rest parameters syntax, allowing it to accept an indefinite number of arguments so we use array of numbers.
You may want to revisit reduce function.
and in the last function, printlength
, item is either a string or an array of string, inside the function we use typeof
to check its type. If it is a string
, we simply log the length of the string otherwise if it is an array, we iterate over each string in the array and log their lengths. This is called Narrowing.
Type Aliases
It is a name for a type, It is used to create reusable types or if you simply want to separate writing types for convenience.
You may capitalize type aliases name to differentiate them from variables and functions.
type Person = {
name: string;
age: number;
};
function greet(p: Person) {
console.log(`Hi ${p.name}, you are ${p.age} years old`);
}
greet({ name: "John Doe", age: 100 });
// Some more examples
type ID = number;
type Name = string;
type Age = number | string;
type NaturalNumbers = number[];
type Body = {
height: number;
width: number;
location?: string;
readonly name: string; // This property cannot be re-assigned.
}
We can use type aliases to create function type expressions.
type func = (a: string) => void; // function type expression
function greets(name: string, callback: func) {
callback(name);
}
greets("John Doe", function (name) {
console.log(`Hi ${name}`);
});
The above type func
means a function with one parameter of type string
and returns nothing.
Interfaces
Interfaces are another way to name an object type. They are similar to Type Aliases but have some distinctions which we'll talk about later.
Syntax
interface Book {
title: string;
author: string;
readonly pageCount?: number;
}
const book: Book = {
title: "The Great Gatsby",
author: "F. Scott Fitzgerald",
};
interface AnotherInterface {
name: string;
func: (a: string) => void; // describe a function inside interface
func2(a: string): void; // another way to describe a function
}
// This is function type, functions in JavaScript are objects which can be called, and we can also add properties to our functions.
interface MathOperation {
(x: number, y: number): number;
}
const add: MathOperation = (x, y) => x + y;
const subtract: MathOperation = (x, y) => x - y;
// Its type aliases equivalent
type MathOperationType = (x: number, y: number) => number;
Some key differences between Types and Interfaces
Types aliases do not support declaration merging while interfaces do.
Declaration merging is adding new properties to an existing interface.
for example,
interface Object {
a: number;
}
interface Object {
b: number;
}
function printobj(o: Object) {
console.log(o.a);
console.log(o.b);
}
Both Interface and types can be extended but have different syntax.
You can read more about these distinctions here.
You can use whatever you like, I generally use interfaces for objects and types for everything else.
Generics
lets say you wanna create a function that accepts an argument and returns it.
function identity(arg: any) {
return arg;
};
let num = identity(5);
This works fine but the issue with this is we lose all our type definitions for the return value of the function. If we pass in an argument of type number
, the function returns the same argument but with type any
.
By using generic this problem can be solved.
function identity<T>(arg: T): T {
return arg;
}
// Two ways of consuming generic functions
let num0 = <number>identity(5); // explicitly passing the T parameter
let num = identity(5); // let the compiler infer it
The T
in the angle bracket is called the Type Variable, you can name it whatever you like.
Generics are used to create reusable components that work with different types of data.
Let's say now we want to pass an array of different types and return it
function identity<T>(arg: T[]) {
return arg;
}
Notice we don't have to specify the return type as the compiler can infer it.
If we want to have a generic type for types with a length property such as string and arrays. We can use something called a constraint.
function identity<T extends { length: number }>(arg: T){
return arg.length;
}
In similar way we can create generic interfaces.
interface Box<T> {
value: T;
};
type NumberBox = Box<number>;
type StringBox = Box<number>;
Tuples
Tuples are array but with a pre-defined length and types for each index.
Syntax
type FirstTuple = [number, string];
type SecondTuple = [number, string, boolean];
const firstTuple: FirstTuple = [1, '2'];
const secondTuple: SecondTuple = [1, '2', true];
FirstTuple is an array of fixed length 2 with index 0 being a number and index 1 being a string. if we try access element past index 1, TypeScript will throw error.
never
Type when function never returns a value, it is similar to void but it is used when function throws an exception or if a function enters an infinite loop.
function throwError(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {
// ...
}
}
Non-null Assertion Operator (Postfix !)
It is used to tell the compiler to assume that a value is not null
or undefined
, even if typescript thinks it could be nullable.
function func(arg?: number) {
console.log(arg!++);
}
Type Assertions
Type Assertions are way to override the type inference and explicitly set the type.
For Example
const btn = document.getElementById("my_button") as HTMLButtonElement;
TypeScript thinks that getElementById will return some kind of HTMLElement, but you can override this by using the as
operator.
Thanks for reading, I tried to condense as much as examples possible and tried cover most frequently used types.
Top comments (0)