Advanced Typescript

Keywords and special datatypes

Introduction

Despite the fact that TypeScript (TS) is frequently touted as a superset of JavaScript (JS) that just adds type safety to existing JavaScript (JS) features, TS comes with advanced data types and features that are not available in JS. Some that will be discussed in this article are Tuples, Enums, and Interfaces, alongside special keywords.

readonly Keyword

If you are creating type aliases and want a field to be unchangeable in any part of the code after declaring it, use the readonly keyword:

type User = { readonly _id: number; name: string; email: string; isPremium: boolean; };

On the _id type alias, call a readonly operation on it. Now, if there's any attempt to mutate the _id after declaring it, static checking will signal an error, like in the example below:

const user: User = {
    _id: 5,
    name: "Kelvin",
    email: "okuroemidouebikelvin@gmail.com",
    isPremium: true
};

user.name = "Fy";
user._id = 78; // Error !!!

Question Marks

When defining type aliases, certain parameters can be optional.

type User = {
    readonly _id: ["a", "b"];
    name: string;
    email: string;
    isPremium: boolean;
    credcardDetails?: number; // Optional alias
};
  • Even if credcardDetails? is not specified when using the User type alias, no static checking is triggered. The question mark simply indicates that it is optional.

Combining Types

Type aliases can be combined with each other to save redundant code typing, i.e., retyping types. For example, you could have three type aliases like this:

type BioData = {
    name: string;
    id: number;
};

type BankData = {
    accountType: string;
};

type CVV = {
    cvv: number;
    name: string;
    id: number;
    accountType: string;
};
  • We have three type aliases, BioData, BankData, and CVV. BioData and BankData have type details that are wanted to create the CVV alias.

  • Copying and pasting the type details of BioData and BankData into CVV is not efficient and makes us repeat code, something TypeScript (TS) wants to help us avoid.

So, in a case where we need the type details of another type alias to use with another type alias, we can do this:

type BioData = {
    name: string;
    id: number;
};

type BankData = {
    accountType: string;
};

type CVV = BioData & BankData & {
    cvv: number;
};
  • Now you don't have to copy and paste the type details of BioData and BankData into CVV to use it.

This means that to use CVV, we still have to adhere to the types of all aliases mixed with it.

let firstUser: CVV = {
    name: "Kelvin",
    id: 55,
    accountType: "Checking",
    cvv: 423
};

Tuples

Tuples are modified arrays. With tuples, the exact sequence of array contents can be defined based on their data types, and if this sequence is not adhered to, it can cause a bug or, in the case of TS, a static checking error.

const rgba: [number, number, number, string] = [53.4, 6.43, 65.5, "transparent"];

It is also possible to have tuple type aliases. This means that a type alias can be defined to have a preset order of data types, so any entity that uses such a type alias must define its contents as the tuple type alias.

type tupleAlias = [number, boolean];
const user: tupleAlias = [46, false];

Note: It is pertinent to note a significant flaw in tuples. Since types are defined for tuples, tuples should follow the defined type aliases, and deviation should induce TS static checking errors. However, this does not happen if the tuple is mutated with array methods.

type tupleAlias = [number, boolean];
const user: tupleAlias = [46, false];
user.push(56); // Contravenes the type alias
  • The last line user.push(56) should signal a TS static checking error since it doesn't follow the type alias tupleAlias, but it does not.

  • At the time of this article, this is an open issue in TS, and there is no solution to this behavior yet.

Enums

Enums mean enumerations. Enums are used for storing different values of strings and numbers for a set of related variables. Enums aid in improving code readability.

enum seat {
    A = "letter A",
    B = 2,
    C = "Enums are great",
    D = "true"
}

const option = seat.C; // Output: "Enums are great"

Interfaces

In TS, an interface is a keyword for mainly defining how objects should be structured. It defines the properties and methods an object should have before fulfilling that. Although similar to types, it comes with its own differences that I will point out shortly. This is how to define interfaces:

interface writer {
    name: string;
    age: number;
    showNumberofArticles(): number;
}
  • If any object or class is created with the writer interface, it has to contain all object fields and methods, or it will trigger a static checking error.

To use an interface:

const Kelvin: writer = {
    name: "Kelvin",
    age: 21,
    showNumberofArticles() {
        return 4;
    }
};

Differences from type

The easiest to spot is that the syntax for type and interface is different.

interface allows for declaration merging. Declaration merging means that two interfaces with the same name can be combined to be used for one object. So, if for some weird reason, you wanted to do that, this is how it can be done:

interface Person {
    name: string;
    age: number;
}

interface Person {
    gender: string;
}

const person: Person = {
    name: "John",
    age: 30,
    gender: "male"
};
  • However, a type cannot be changed after declaration.

Interfaces can be extended with the extends keyword. This allows for the creation of hierarchies and inheritance of interface properties:

interface Animal {
    name: string;
    eat(): void;
}

interface Dog extends Animal {
    breed: string;
    bark(): void;
}

const dog: Dog = {
    name: "Buddy",
    breed: "Labrador",
    eat() {
        console.log(`${this.name} is eating.`);
    },
    bark() {
        console.log(`${this.name} is barking.`);
    }
};
  • The Animal interface is created, and the Dog interface inherits its specifications through the extends keyword.

  • The dog variable then uses the Dog interface and has to satisfy all the requirements for both Animal and Dog.

Conclusion

TypeScript's advanced features, such as readonly keywords, optional parameters, type combinations, tuples, enums, and interfaces, empower developers to write cleaner, safer, and more maintainable code. These tools go beyond what JavaScript offers, enhancing both productivity and scalability in modern development. While some issues, like tuple mutation, remain unresolved, TypeScript continues to evolve, cementing its place as a powerful asset for developers looking to enforce strong typing and reduce runtime errors. Whether you're just starting with TypeScript or refining your skills, leveraging these features will undoubtedly improve your development workflow.