Y
Published on

Mastering Classes and Interfaces in TypeScript

Authors
  • avatar
    Name
    Yinhuan Yuan
    Twitter

Introduction

TypeScript extends JavaScript's object-oriented capabilities with a robust system of classes and interfaces. These features provide powerful tools for creating structured, maintainable, and scalable code. In this blog post, we'll explore the ins and outs of classes and interfaces in TypeScript.

Classes in TypeScript

Classes in TypeScript offer a familiar syntax for developers coming from other object-oriented languages, while also providing strong typing and other TypeScript-specific features.

Basic Class Syntax

Here's a basic example of a class in TypeScript:

class Person {
  name: string
  age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }

  greet() {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`)
  }
}

const alice = new Person('Alice', 30)
alice.greet() // Outputs: Hello, my name is Alice and I'm 30 years old.
Access Modifiers

TypeScript provides three access modifiers: public, private, and protected.

class Employee {
  private id: number
  protected department: string
  public name: string

  constructor(id: number, name: string, department: string) {
    this.id = id
    this.name = name
    this.department = department
  }
}
  • public: Accessible from anywhere (default)
  • private: Only accessible within the class
  • protected: Accessible within the class and its subclasses
Inheritance

TypeScript supports single inheritance using the extends keyword:

class Manager extends Employee {
  private reports: Employee[]

  constructor(id: number, name: string, department: string) {
    super(id, name, department)
    this.reports = []
  }

  addReport(employee: Employee) {
    this.reports.push(employee)
  }
}
Static Members

Static members belong to the class itself rather than to instances of the class:

class MathOperations {
  static PI: number = 3.14159

  static calculateCircumference(radius: number): number {
    return 2 * MathOperations.PI * radius
  }
}

console.log(MathOperations.PI) // 3.14159
console.log(MathOperations.calculateCircumference(5)) // 31.4159
Abstract Classes

Abstract classes serve as base classes for other classes but cannot be instantiated directly:

abstract class Shape {
  abstract calculateArea(): number

  displayArea() {
    console.log(`The area is ${this.calculateArea()}`)
  }
}

class Circle extends Shape {
  constructor(private radius: number) {
    super()
  }

  calculateArea(): number {
    return Math.PI * this.radius ** 2
  }
}

const circle = new Circle(5)
circle.displayArea() // Outputs: The area is 78.53981633974483

Interfaces in TypeScript

Interfaces define contracts in your code and provide a way to define custom types.

Basic Interface Syntax

Here's a simple interface definition:

interface Person {
  name: string
  age: number
  greet(): void
}

let user: Person = {
  name: 'Alice',
  age: 30,
  greet() {
    console.log(`Hello, I'm ${this.name}`)
  },
}
Optional Properties

Interfaces can have optional properties, marked with a ?:

interface Config {
  color?: string
  width?: number
}

function createBox(config: Config): { color: string; area: number } {
  let newBox = { color: 'white', area: 100 }
  if (config.color) {
    newBox.color = config.color
  }
  if (config.width) {
    newBox.area = config.width ** 2
  }
  return newBox
}
Readonly Properties

You can make properties readonly, which prevents them from being changed after initialization:

interface Point {
  readonly x: number
  readonly y: number
}

let p1: Point = { x: 10, y: 20 }
// p1.x = 5; // Error: Cannot assign to 'x' because it is a read-only property.
Extending Interfaces

Interfaces can extend one or more interfaces:

interface Shape {
  color: string
}

interface Square extends Shape {
  sideLength: number
}

let square: Square = {
  color: 'blue',
  sideLength: 10,
}
Implementing Interfaces in Classes

Classes can implement one or more interfaces:

interface Printable {
  print(): void
}

interface Loggable {
  log(): void
}

class Console implements Printable, Loggable {
  print() {
    console.log('Printing...')
  }

  log() {
    console.log('Logging...')
  }
}

Classes vs Interfaces

While both classes and interfaces can be used to define object shapes, they have different use cases:

  • Use classes when you need to create instances, implement logic, or use inheritance.
  • Use interfaces when you want to define a contract for object shapes or create complex types.

Interfaces are often preferred for defining the shape of objects, especially in cases where you're not creating instances:

interface UserData {
  id: number
  name: string
  email: string
}

function processUser(user: UserData) {
  // Process the user data
}

Conclusion

Classes and interfaces are fundamental building blocks in TypeScript that enable you to write more structured and type-safe code. Classes provide a way to create reusable components with logic and state, while interfaces allow you to define contracts and complex types.

By leveraging these features, you can create more robust and maintainable codebases. Remember that TypeScript's structural typing system means that objects don't need to explicitly implement an interface to be considered compatible with it – they just need to have the right shape.

As you continue to work with TypeScript, you'll find that the judicious use of classes and interfaces can significantly improve the clarity and reliability of your code. Happy coding!