TypeScript Chapter 2

In the first chapter of TypeScript, we learned about the basics of type annotations, classes and interfaces, functions, primitive types, and advanced types. In this chapter, we will learn more about advanced usage of type annotations, class inheritance and polymorphism, generics, modules and namespaces and other advanced features.

Advanced Type Annotations

In the first chapter of TypeScript, we covered the basic usage of type annotations. In this section, we will introduce advanced usage of type annotations, including type aliases, intersection types, union types, and type guards, etc.

type alias

Type aliases help us define more memorable and usable names for complex types. Here is an example of a type alias:

type Person = {
    
    
  name: string;
  age: number;
};

function greet(person: Person) {
    
    
  console.log(`Hello, ${
      
      person.name}!`);
}

In this example, we use the type keyword to define a type alias named Person that represents an object type that has name and age properties. In the greet function, we use the Person type alias to specify the type of the parameter person.

cross type

Intersection types can help us combine multiple types into one. Here is an example of an intersection type:

type Cat = {
    
    
  name: string;
  meow(): void;
};

type Fish = {
    
    
  swim(): void;
};

type CatFish = Cat & Fish;

let catFish: CatFish = {
    
    
  name: "Tom",
  meow() {
    
    
    console.log(`${
      
      this.name} says meow`);
  },
  swim() {
    
    
    console.log(`${
      
      this.name} is swimming`);
  },
};

In this example, we use the & operator to combine the Cat and Fish types into one type alias CatFish. Then, we create an object catFish of type CatFish, which contains properties and methods of type Cat and Fish.

union type

Union types help us specify multiple possible types of a variable. The following is an example of a union type:

let value: string | number;

value = "hello";
console.log(value.length);

value = 10;
console.log(value.toFixed(2));

In this example, we use the | operator to specify the type of variable value as string or number. When we assign a variable as a string, we can use the length property of the string; when we assign the variable as a number, we can use the toFixed method of the number.

type protection

When we use union types, we need to use type guards to determine the concrete type of the variable. Here is an example of a type guard:

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

printValue("hello");
printValue(10.123);

In this example, we use the typeof operator to determine the type of the variable value. When the type of the variable is a string, we can use the toUpperCase method of the string; when the type of the variable is a number, we can use the toFixed method of the number.

Class Inheritance and Polymorphism

Class inheritance and polymorphism are one of the core concepts of object-oriented programming, they can help us organize code and achieve code reuse. In TypeScript, class inheritance and polymorphism are also possible.

class inheritance

Class inheritance can help us derive new classes from existing classes and extend their functionality. The following is an example of inheritance for a class:

class Animal {
    
    
  name: string;

  constructor(name: string) {
    
    
    this.name = name;
  }

  sayHello() {
    
    
    console.log(`Hello, my name is ${
      
      this.name}.`);
  }
}

class Cat extends Animal {
    
    
  meow()() {
    
    
    console.log(`${
      
      this.name} says meow`);
  }
}

let cat = new Cat("Tom");
cat.sayHello();
cat.meow();

In this example, we define an Animal class that contains a property called name and a method called sayHello. Then, we define a Cat class that inherits the name property and sayHello method from the Animal class, and extends a method called meow. Finally, we create an instance cat of the Cat class and call its sayHello and meow methods.

polymorphism

Polymorphism helps us to use the reference of the base class to call the method of the derived class. Here is an example of polymorphism:

class Animal {
    
    
  name: string;

  constructor(name: string) {
    
    
    this.name = name;
  }

  sayHello() {
    
    
    console.log(`Hello, my name is ${
      
      this.name}.`);
  }
}

class Cat extends Animal {
    
    
  meow() {
    
    
    console.log(`${
      
      this.name} says meow`);
  }
}

class Dog extends Animal {
    
    
  bark() {
    
    
    console.log(`${
      
      this.name} says woof`);
  }
}

let animals: Animal[] = [new Cat("Tom"), new Dog("Max")];

for (let animal of animals) {
    
    
  animal.sayHello();

  if (animal instanceof Cat) {
    
    
    animal.meow();
  } else if (animal instanceof Dog) {
    
    
    animal.bark();
  }
}

In this example, we define a class Animal and two derived classes Cat and Dog. Then, we create an array animals of type Animal, which contains an instance of type Cat and an instance of type Dog. Finally, we loop through the animals array and use the reference of the base class to call the method of the derived class.

generic

Generics can help us write more general and flexible code. In TypeScript, generics can be applied to scenarios such as functions, classes, and interfaces.

generic function

Generic functions help us to write functions that can be applied to multiple types. Here is an example of a generic function:

function reverse<T>(items: T[]): T[] {
    
    
  return items.reverse();
}

let numbers = [1, 2, 3, 4, 5];
let reversedNumbers = reverse(numbers);
console.log(reversedNumbers);

let strings = ["hello", "world"];
let reversedStrings = reverse(strings);
console.log(reversedStrings);

In this example, we define a generic function called reverse that takes an array of type T as a parameter and returns an array of type T. Then, we called the reverse function separately, and passed an array of numeric type and an array of string type as parameters.

generic class

Generic classes help us write classes that can apply to multiple types. Here is an example of a generic class:

class Stack<T> {
    
    
  private items: T[] = [];

  push(item: T) {
    
    
    this.items.push(item);
  }

  pop(): T | undefined {
    
    
    return this.items.pop();
  }
}

let stack = new Stack<number>();
stack.push(1);
stack.push(2);
stack.push(3);
console.log(stack.pop());
console.log(stack.pop());
console.log(stack.pop());

let stringStack = new Stack<string>();
stringStack.push("hello");
stringStack.push("world");
console.log(stringStack.pop());
console.log(stringStack.pop());

In this example, we define a generic class called Stack that contains a private property called items and two public methods push and pop. Then, we created an instance of Stack type and an instance of Stack type respectively, and called their push and pop methods respectively.

generic interface

Generic interfaces help us define interfaces that can apply to multiple types. The following is an example of a generic interface:

interface Pair<T, U> {
    
    
  first: T;
  }

In this example, we define a generic interface Pair with two type parameters T and U. This interface has a property first whose type is T.

The above are some basics of TypeScript's type system. In the following studies, we will dig into these concepts and learn more advanced features.

Guess you like

Origin blog.csdn.net/ZTAHNG/article/details/131205873