Transitioning to TypeScript: The Ultimate Starter Guide - Part 4

TypeScript from JavaScript Part 4 of 7: Advanced Types and Type Guards

Welcome back to our series on transitioning from JavaScript to TypeScript! If you’ve been following along, by now you should have a good grasp on the basics of TypeScript, including modules, namespaces, and decorators which we covered in the last part. In this part, we are going to dive into advanced types and type guards in TypeScript.

Remember, our goal here is not just to learn TypeScript, but also to understand how it can simplify our work, save time, and ultimately elevate our apps. So let's get started!

1. Advanced Types

TypeScript comes with a set of advanced types that help you to model your data more precisely. Let's go through some of the most useful ones.

a. Union Types

Union types allow you to define a variable that can be of multiple types. You can define a union type using the | operator.

let value: number | string;

value = 10;
value = 'Hello';

In this example, the value variable can be either a number or a string.

b. Intersection Types

Intersection types allow you to combine multiple types into one. You can define an intersection type using the & operator.

interface Person {
  name: string;
}

interface Employee {
  id: number;
}

type EmployeeRecord = Person & Employee;

const employee: EmployeeRecord = {
  name: 'Alice',
  id: 1
};

In this example, the EmployeeRecord type is an intersection of the Person and Employee types.

c. Literal Types

Literal types allow you to restrict a variable to a specific value.

type Direction = 'north' | 'south' | 'east' | 'west';

const direction: Direction = 'north';

In this example, the direction variable can only be one of the four specified values.

d. Mapped Types

Mapped types allow you to create a new type from an existing type by transforming its properties.

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

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

type ReadonlyPerson = Readonly<Person>;

const person: ReadonlyPerson = {
  name: 'Alice',
  age: 30
};

person.name = 'Bob'; // Error

In this example, the ReadonlyPerson type is a mapped type that makes all properties of the Person type read-only.

e. Conditional Types

Conditional types allow you to define a type based on a condition.

type IsString<T> = T extends string ? true : false;

type A = IsString<string>; // true
type B = IsString<number>; // false

In this example, the IsString type is a conditional type that checks if a type is string.

2. Type Guards

Type guards are expressions that perform a runtime check on the type of a variable.

a. typeof Type Guards

The typeof operator can be used as a type guard to check the type of a variable.

function print(value: number | string) {
  if (typeof value === 'number') {
    console.log(value.toFixed(2));
  } else {
    console.log(value.toUpperCase());
  }
}

print(10); // 10.00
print('hello'); // HELLO

In this example, the typeof operator is used as a type guard to check if the value variable is a number or a string.

b. instanceof Type Guards

The instanceof operator can be used as a type guard to check the instance of a class.

class Circle {
  radius: number;

  constructor(radius: number) {
    this.radius = radius;
  }
}

class Square {
  side: number;

  constructor(side: number) {
    this.side = side;
  }
}

function getArea(shape: Circle | Square) {
  if (shape instanceof Circle) {
    return Math.PI * Math.pow(shape.radius, 2);
  } else {
    return Math.pow(shape.side, 2);
  }
}

const circle = new Circle(5);
const square = new Square(5);

console.log(getArea(circle)); // 78.53981633974483
console.log(getArea(square)); // 25

In this example, the instanceof operator is used as a type guard to check if the shape variable is an instance of Circle or Square.

c. User-Defined Type Guards

You can also create your own type guards by defining a function that returns a type predicate.

interface Person {
  name: string;
}

function isPerson(value: any): value is Person {
  return value && typeof value.name === 'string';
}

function printName(value: any) {
  if (isPerson(value)) {
    console.log(value.name);
  } else {
    console.log('Not a person');
  }
}

const person = { name: 'Alice' };
const notPerson = { age: 30 };

printName(person); // Alice
printName(notPerson); // Not a person

In this example, the isPerson function is a user-defined type guard that checks if a value is a Person.

Conclusion

In this part, we learned about advanced types and type guards in TypeScript. These features are extremely powerful and allow you to model your data more precisely and perform runtime type checks.

In the next part of this series, we will learn about async/await and error handling in TypeScript. Stay tuned!

That's all for now, happy coding!

Did you find this article valuable?

Support Innovate Sphere by becoming a sponsor. Any amount is appreciated!