TypeScript 102: A Deep Dive into Advanced Types

Introduction

TypeScript 101, now 102. I'm not quite sure how I came up with the naming scheme for these pieces, but I like it. If you're new to TypeScript, I'd recommend checking out my most recent article "TypeScript 101: A Comprehensive Guide to Types", just so you have that basic foundation on how types work and how it can make your development life a breeze.

In this article, we'll cover advanced concepts like tuples, enums, interfaces, generics, and type guards. By the end of it, you'll have a solid understanding of these concepts and be on your way to mastering TypeScript.

Tuples

Tuples are like arrays but with a little extra control. You get to call the shots on how many elements you want and what data type each element should be. To define a tuple, all you have to do is set the data types for each element and define the array. It’s that easy!

let randomValues:[string, number, boolean] = ["Analiese", 50, false]

console.log(randomValues)

Enums

Enums, short for "enumeration" is a valuable concept in TypeScript for keeping your code organized and maintainable. It offers a feature that provides intellisense while you are typing, making it even easier for you to pick the correct option value and write codes with confidence.

Enums is not a native feature of JavaScript but Microsoft introduced them to TypeScript to make the language feel more object-oriented. How does it work then?

Let’s try to create a report card comment generator that displays comments based on each student’s grade . Instead of using numbers or strings to represent these grades, we could utilize the concepts of enums by defining an object variable using the "enum" keyword.

enum Grade {
  A, B, C, D, E
}
let comment = Grade.A

if(comment === Grade.A){
  console.log("Kudos! you aced your exam!")
}

Interfaces

When you're just starting with a small project, you might be able to get away without using interfaces. But as your codebase gets bigger and more complex, it becomes really important to have clear, well-defined types. With interfaces, you can make sure that your objects and classes have the specific properties and methods that they're supposed to have.

To define interfaces in TypeScript, all you need to do is to use the "interface" keyword, followed by the interface's name, and then pass the interface's properties and methods within the curly brackets.

interface Student {
  name:string,
  id:number;
  year:number;
  Registeredsubjects:string[];
  getSubjects(): string;
}

const StudentData:Student = {
  name:'Analiese',
  id:264235,
  year:6,
  Registeredsubjects: ['english', 'math', 'creative art', 'history'],
  getSubjects(): string {
    return this.Registeredsubjects.join(", ");
  }
}

console.log(`${StudentData.name} is a year ${StudentData.year} student with her id number as ${StudentData.id} and her registered subjects are ${StudentData.getSubjects()}`)

In this example, I created an interface called 'student' that defines certain properties and a method that a student object must possess. By assigning the type 'student' to the 'studentData' object, I can ensure that the properties of the studentData object match the properties defined in the interface and if any inconsistency arises, a type error will be thrown, guaranteeing that the object remains consistent throughout the codebase.

Generics

I know a lot of people think the concept of generics is complex but it isn't. With generics, you have the freedom to use any type you want, without being locked into one specific type. It's like the 'any' type in TypeScript but even better 'cause it gives you specific type definitions for your declarations. Let's dive in and see how it works.

//generic function that can return any type of data
function testingGenerics<T>(value: T){
  return value;
}

let numberValue = testingGenerics(5);
console.log(numberValue); 

let stringValue = testingGenerics("hello");
console.log(stringValue); 

let objectValue = testingGenerics({ name: "John", age: 30 });
console.log(objectValue);

In this example, I created a generic function that accepts a parameter of generic type T. This means that the function argument can assume any type without causing errors.

Type Guards

Unlike other concepts we've discussed, type guards are a familiar concept that has been around in JavaScript for a while. They allow you to take a broad type and narrow it down to something more specific. It is a handy tool that helps you identify the type of a variable at runtime which can be incredibly useful when handling different types of data in a single function or in validating user inputs.

Among the various type guards available, the typeof operator is one of the most commonly used. Some other options available is the instanceof operator, the in operator, the assignment operator and more. But, for simplicity sake, we will focus only on the typeof operator.

Let's try to validate the user inputs before performing any action, we would do this by checking the type of the input, and returning an error message if the input is not of the expected type.

function userInput(data: string|number) {
  if (typeof data === 'string') {
    return data;
  } else if (typeof data === 'number') {
    return data;
  } else {
    return 'invalid input';
  }
}
console.log(userInput('hello')); // 5
console.log(userInput(3)); // 6
console.log(userInput([1,2,3])); // 'invalid input'

Since the userInput function is designed to handle only strings and integers as data types, passing an array as an argument causes the TypeScript compiler to report an error and display the message "Invalid input" in the console.

Conclusion

And that's a wrap on "TypeScript 102: A Deep Dive into Advanced Types"! You now know all the ins and outs of tuples, enums, interfaces, generics, and type guards, and you're well on your way to becoming a TypeScript developer.