Part 3: Advanced TypeScript Concepts

Published on
Authors

Section 3: Advanced TypeScript Concepts

In this section, we will explore advanced concepts in TypeScript that allow us to write more expressive and robust code. We will cover union and intersection types, type inference, generics, and type guards.

Union and Intersection Types

Union types allow us to specify that a value can be of multiple types. We use the | operator to denote a union type.

function printId(id: number | string): void {
  console.log(`ID: ${id}`)
}

printId(123) // Valid
printId('abc') // Valid
printId(true) // Error: Argument of type 'boolean' is not assignable to parameter of type 'number | string'

Intersection types allow us to combine multiple types into a single type. We use the & operator to denote an intersection type.

interface Printable {
  print: () => void
}

interface Loggable {
  log: () => void
}

function createLogger(): Printable & Loggable {
  return {
    print: () => console.log('Printing...'),
    log: () => console.log('Logging...'),
  }
}

const logger = createLogger()
logger.print() // Valid
logger.log() // Valid
logger.sayHello() // Error: Property 'sayHello' does not exist on type 'Printable & Loggable'

Type Inference

TypeScript's type inference system allows the compiler to deduce the types of variables and expressions based on their context and usage. This reduces the need for explicit type annotations in many cases.

let message = 'Hello, TypeScript!' // TypeScript infers the type as string
let count = 5 // TypeScript infers the type as number

function add(a: number, b: number) {
  return a + b // TypeScript infers the return type as number
}

const person = {
  name: 'John Doe',
  age: 30,
} // TypeScript infers the type as { name: string, age: number }

Type inference enhances code readability and maintainability while still providing type safety.

Generics

Generics in TypeScript allow us to create reusable components that can work with a variety of types. They enable us to write type-safe code while maintaining flexibility.

function toArray<T>(arg: T): T[] {
  return [arg]
}

const numbers = toArray(1) // numbers inferred as number[]
const names = toArray('John', 'Jane') // names inferred as string[]
const values = toArray(1, 'two') // Error: Argument of type 'string' is not assignable to parameter of type 'number'

Generics provide a way to abstract over types and create more flexible and reusable code.

Type Guards

Type guards allow us to narrow down the type of a variable within a conditional block, based on a runtime check. This enables us to write more precise and type-safe code.

function logLength(value: string | string[]): void {
  if (typeof value === 'string') {
    console.log(value.length) // Valid: value is narrowed down to string
  } else {
    console.log(value.length) // Valid: value is narrowed down to string[]
  }
}

Type guards help us write code that handles different types correctly and prevents runtime errors.


In the next section, we will learn about modules and imports in TypeScript, which allow us to organize and modularize our codebase.