All you need to know about “Types” in Typescript

Typescript Types

Typescript is a superset of Javascript equipped with static type. Typescript is becoming vastly popular web development language for different Javascript based frameworks and runtimes like React, Angular, Node.js to name a few. In this Article we are going to explore different data types that typescript offers.

Typescript is a superset of Javascript equipped with static type. Typescript is becoming vastly popular web development language for different Javascript based frameworks and runtimes like React, Angular, Node.js to name a few. In this Article we are going to explore different data types that typescript offers.

Primitive type

There are five primitive types available in typescript

1. Number

To define any type of numeric value whether it’s a floating point number, integer, hexadecimal, binary, or octal “number” is the go-to type in Typescript. Let’s see some examples of how to declare numeric variables in Typescript

let m: number = 20;
let n: number = 34.5;
let p: number = 0xf00d; //Hexadecimal number
let q: number = 0b1010; // Binary number
let r: number = 0o744; // Octal number

2. String

To define any type of string value for a variable, it must be declared as a “string” in Typescript. let’s see some examples of strings

let animal: string = 'Lion';
let education: string = "M.Tech"

3. Boolean

Boolean variables in Typescript are declared as “boolean”. Let’s see examples of how variables are declared as boolean in Typescript

let isActive: boolean = true
let isLoggedin: boolen = false

4. Null

in Typescript null is both a datatype and value . Where as in Javascript null is a value but its type is object. It represents nothingness or empty value. Let’s see how we declare a variable as null in Typescript

let nl: null = null

Null is a subtype of other types , which means null is assignable to number, string etc. But when using  strictNullChecks flag as true in tsconfig.json file then null is only assignable to unknownany and their respective types. it helps to avoid lots of potential errors in your code

5. Undefined

In Typescript undefined is a type as well as value just like null. Like null it also represents nothingness. Lets see an example of undefined

let und: undefined = undefined

Like null, undefined is also a subtype of other types. which means null is assignable to a number, string, etc. But when using  the strictNullChecks flag as true in the tsconfig.json file then undefined is only assignable to unknow, any and void.

Reference Types

Reference types doesn’t have a fixed size and dynamic in nature. There are three types of reference types in Typescript

1. Array

Array in Typescript can hold values of same type. The data types of values can be ‘string’, ‘number’ , objects etc. You can declare an array in typescript in two different ways. Let’s see some examples of how we can declare arrays of different kind in typescript

let numarr: number[] = [11,12,13,14,15]
let numberarr: Array<number> = [20,30,40,50]
let strArr: string[] = ['Welcome','Thank You', 'Bye']
let nameArr: Array<string> =['John','Jay','Joy','Jean']

2. Object

One of the most common types of data that we encounter in Javscript is of Object type. To define an object type variable we simple list the properties with their types

function printLatLang({lat: number,lang: number}): string{
  return `The Latitude is ${lat} and Longitude is ${lang}`;  
}

3. Tuples

At their core, tuples are similar to arrays. However, while arrays typically have elements of a singular type (e.g., an array of numbers or strings), tuples allow for a combination of types. Each position in a tuple has a designated type, and the order matters.

let person: [string, number];
person = ["John", 30];   // This is valid
person = [30, "Jay"];   // This would raise a type error

Other Available Types in Typescript

1. Any

TypeScript also has a special type, any", that you can use whenever you don’t want a particular value to cause typechecking errors.

let notSure: any = 4;
notSure = "maybe a string";

The any type is useful when you don’t want to write out a long type just to convince TypeScript that a particular line of code is okay. Generally if you don’t declare a type for a variable in Typescript , the complier infer it as any type. If in tsconfig.json we declare noImplicitAny property as true then Typescript will throw error for any implicit any

2. Void

When there is no type involved void is used. It is most commonly used for function return types when a function doesn’t return any value.

function warnUser(): void {
  console.log("This is a warning message");
}

3. Enum

A helpful addition to the standard set of datatypes from JavaScript is the enum . An enum is a way of giving more friendly names to sets of numeric values.

enum OrderStatus {Pending, Dispatched, Shipped, Delivered, Cancelled}
let o: OrderStatus = OrderStatus.Shipped;

By default, enums begin numbering their members starting at 0. But you can manually set the values of the enum

enum OrderStatus={
 Pending = 1
 Dispatched = 2
 Shipped = 4
 Delivered = 3
 Cancelled = 7
}
let o: OrderStatus = OrderStatus.Shipped;

4. Unknown

From Typescript version 3.0 and above a special type has been introduced which is ‘unknown’ type. When you don’t know exactly what will be the type of the variable or a return type of a function ‘unknown’ can be used. Though ‘unknown’ and ‘any’ can hold any values but unknown is proffered over any because it provides safer typing.

let x: unknown = 0;
x = '1';
x= false;

5. Union Type

When a particular variable can consume multiple types of values in Typescript we can combine those types while defining the type for the variable

let value: number | string;
value = 123;   // ok
value = '123'; // ok

6. Literal Type

In TypeScript, a literal type represents an exact value that a variable can hold. It narrows down the type to a much more specific set of allowable values. Literal types are often combined with union types to represent a value that can be one of several predefined values.

String Literal Types

String literal types allow you to specify the exact string values a string can take.

type Direction = "North" | "South" | "East" | "West";

let move: Direction;
move = "North";   // OK
move = "Northeast";  // Error: Type '"Northeast"' is not assignable to type 'Direction'

Numeric Literal Types

Similar to string literal types, but for numbers.

type DiceValue = 1 | 2 | 3 | 4 | 5 | 6;

let roll: DiceValue;
roll = 4;   // OK
roll = 7;   // Error: Type '7' is not assignable to type 'DiceValue'

Boolean Literal Types

Boolean types in TypeScript already behave like literal types where they can only be assigned true or false. But for completeness’ sake:

type TrueOnly = true;

let isTrue: TrueOnly;
isTrue = true;    // OK
isTrue = false;   // Error: Type 'false' is not assignable to type 'true'

Template Literal Types (introduced in TypeScript 4.1)

With TypeScript 4.1 and later, you can create new string types by manipulating string literal types using template literal patterns:

type World = "world";
type Greeting = `Hello ${World}`;

const greet: Greeting = "Hello world"; // OK

Along with these types some of the concepts related to types we must know in order to get the whole essence of Typescript

Type Guards

In TypeScript, ensuring a variable conforms to a specific type at runtime becomes essential, especially since JavaScript lacks static typing. Here, the concept of “Type Guards” comes into play. Type guards are expressions that perform runtime type checking, allowing TypeScript to narrow down the type within a specific block.

Common Methods for Type Guarding:

typeof: It’s one of the basic type guards and checks whether a variable is a certain basic type (string, number, boolean, object, or function).

function padLeft(value: string | number, padding: string | number) {
    if (typeof padding === "number") {
        return Array(padding + 1).join(" ") + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
}

instanceof: This guard is used to check if an object is an instance of a class or a constructor.

class Bird {
    fly() {
        console.log("Bird flies");
    }
}

class Fish {
    swim() {
        console.log("Fish swims");
    }
}

function move(animal: Bird | Fish) {
    if (animal instanceof Bird) {
        animal.fly();
    } else {
        animal.swim();
    }
}

Here, the instanceof type guard ensures the appropriate method (fly or swim) is called based on the passed animal’s actual type.

User-defined Type Guards: When built-in guards aren’t sufficient, you can create your own type guard by defining a function whose return type is a type predicate.

function isFish(animal: Bird | Fish): animal is Fish {
    return (animal as Fish).swim !== undefined;
}

function move(animal: Bird | Fish) {
    if (isFish(animal)) {
        animal.swim();
    } else {
        animal.fly();
    }
}

The animal is Fish is a type predicate. If isFish returns true, TypeScript knows animal must be of type Fish within that block.

Type Aliases

Type aliases are a powerful feature in TypeScript’s toolkit, allowing developers to create more readable, maintainable, and organized code. By providing a way to label and encapsulate complex type structures, type aliases streamline code and improve its clarity.

Defining Type Aliases

To create a type alias, you use the type keyword, followed by your desired name for the type, an equals sign, and then the type definition.

type StringOrNumber = string | number;
let str: StringOrNumber = 'Magic'
let num: StringOrNumber = 321

Here, StringOrNumber can be used wherever you want a type that can be either a string or a number.

One unique feature of type aliases is that they can refer to themselves, allowing the creation of recursive types.

type TreeNode = {
    value: string;
    children: TreeNode[];
};

Interfaces

An interface declaration is another way to name an object type:

interface Person {
  firstName: string;
  lastName: string;
  age: number
}

function printPerson(pr: Person): void{
   console.log(`My name is ${firstName} ${lastName} and I am ${age} years old`);
}

let pr1 = {
  firstName: 'John',
  lastName: 'Doe',
  age: 30
}

printPerson(pr1)

Difference between Interface and Type Aliases

Feature/AspectInterfacesType Aliases
DefinitionUsed to define the structure of an object or other custom types.Can represent any valid type, not just the structure of objects.
Declaration MergingSupports declaration merging. Multiple declarations with the same name are merged.

interface User {
name: string;
}
interface User {
age: number;
}
let user: User = {
name: “Alice”,
age: 30
}; // Works fine
Does not support declaration merging. Duplication results in an error.
ExtensibilityCan be extended using extends. Can be implemented by classes using implements.Cannot be extended or implemented in the same way. Uses intersection types (&) to combine types.
Use CasesPrimarily for object shapes, classes, and other OOP constructs.Versatile; can represent primitives, unions, intersections, tuples, recursive types, etc.
Descriptive ErrorsTypically clearer error messages when used in function signatures or expected types.Might have more verbose error messages for complex types.
CompatibilityPresent from the initial versions of TypeScript.Introduced later; advanced features may not be available in older TypeScript versions.
Literal TypesCannot be used to directly define literal types.Can directly define literal types (e.g., `type Direction = “left”
Recursive ReferencesCan reference themselves.Can also reference themselves, often used in more complex constructs.

Generics

Generics are a feature in TypeScript that allows you to write reusable, type-safe code without committing to a single data type. This capability facilitates the creation of flexible and reusable components without compromising type safety. Generics are common in languages like Java and C#, and TypeScript brings this powerful feature to the JavaScript ecosystem.

Why Use Generics?

Imagine writing a function to return the last item in an array. Without generics, you could return any, but this would sacrifice type information. With generics, you can return the exact type of item in the array.

Basic Usage:

Here’s a basic example of a generic function:

function identity<T>(arg: T): T {
    return arg;
}

In this function, T is a type variable. It stands in for any type, and the consumer of the function gets to choose what type that is.

Usage:

let output1 = identity<string>("myString");
let output2 = identity<number>(100);

Generic Types:

You can also define generic types. Using the identity function from before:

let myIdentity: <T>(arg: T) => T = identity;

Generic Interfaces:

Interfaces can also be generic. Here’s how you might rewrite the above using an interface:

interface GenericIdentityFn<T> {
    (arg: T): T;
}

let myIdentity: GenericIdentityFn<number> = identity;

Generic Classes:

Classes can have generic properties and methods:

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

In this Article we have tried to cover most of the aspects of TypeScript’s type system, but there are many nuanced features and patterns that developers can utilize. Checking the official TypeScript documentation is highly recommended for a comprehensive understanding.

https://www.typescriptlang.org/docs/handbook/

Dcokerize Node.js/Typescript/MongoDB app Previous post Running Node.js/Typescript application with MongoDB in Docker container
Next post All you need to know about setting up MongoDB Atlas for your application: A Visual Guide