Skip to content

Advanced TypeScript Tutorial ​

This tutorial will explore advanced TypeScript features and best practices to help you become a TypeScript expert.

Advanced Types ​

Union and Intersection Types ​

typescript
// Union types
type Status = 'loading' | 'success' | 'error'
type ID = string | number

// Intersection types
interface User {
  name: string
  email: string
}

interface Admin {
  permissions: string[]
}

type AdminUser = User & Admin

const adminUser: AdminUser = {
  name: 'John',
  email: 'john@example.com',
  permissions: ['read', 'write', 'delete']
}

Conditional Types ​

typescript
// Basic conditional types
type IsString<T> = T extends string ? true : false

type Test1 = IsString<string>  // true
type Test2 = IsString<number>  // false

// Useful conditional types
type NonNullable<T> = T extends null | undefined ? never : T
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any

// Distributive conditional types
type ToArray<T> = T extends any ? T[] : never
type StrArrOrNumArr = ToArray<string | number>  // string[] | number[]

Mapped Types ​

typescript
// Basic mapped types
type Readonly<T> = {
  readonly [P in keyof T]: T[P]
}

type Partial<T> = {
  [P in keyof T]?: T[P]
}

// Custom mapped types
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}

interface Person {
  name: string
  age: number
}

type PersonGetters = Getters<Person>
// {
//   getName: () => string
//   getAge: () => number
// }

Template Literal Types ​

typescript
// Basic template literal types
type Greeting = `Hello ${string}`
type EmailLocaleIDs = `${string}_${string}`

// Combined with mapped types
type EventNames<T extends string> = `${T}Changed`
type PersonEvents = EventNames<'name' | 'age'>  // 'nameChanged' | 'ageChanged'

// Practical example: CSS properties
type CSSProperties = {
  [K in 
    | 'color' 
    | 'background-color' 
    | 'font-size'
    as `--${K}`
  ]?: string
}

const styles: CSSProperties = {
  '--color': 'red',
  '--background-color': 'blue',
  '--font-size': '16px'
}

Advanced Generics ​

Generic Constraints ​

typescript
// Basic constraints
interface Lengthwise {
  length: number
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length)
  return arg
}

// Using type parameters to constrain
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}

const person = { name: 'John', age: 30 }
const name = getProperty(person, 'name')  // string
const age = getProperty(person, 'age')    // number

Generic Utility Types ​

typescript
// Custom utility types
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]
}

type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
}

// Function overloads with generics
function createElement<T extends keyof HTMLElementTagNameMap>(
  tag: T
): HTMLElementTagNameMap[T]
function createElement<T extends HTMLElement>(
  tag: string
): T
function createElement(tag: string): HTMLElement {
  return document.createElement(tag)
}

const div = createElement('div')      // HTMLDivElement
const span = createElement('span')    // HTMLSpanElement
const custom = createElement<HTMLElement>('custom')  // HTMLElement

Decorators ​

Class Decorators ​

typescript
// Class decorator
function sealed(constructor: Function) {
  Object.seal(constructor)
  Object.seal(constructor.prototype)
}

function classDecorator<T extends { new (...args: any[]): {} }>(
  constructor: T
) {
  return class extends constructor {
    newProperty = 'new property'
    hello = 'override'
  }
}

@sealed
@classDecorator
class BugReport {
  type = 'report'
  title: string

  constructor(t: string) {
    this.title = t
  }
}

Method Decorators ​

typescript
// Method decorator
function enumerable(value: boolean) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    descriptor.enumerable = value
  }
}

function log(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value

  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with args:`, args)
    const result = originalMethod.apply(this, args)
    console.log(`Result:`, result)
    return result
  }
}

class Calculator {
  @enumerable(false)
  @log
  add(a: number, b: number): number {
    return a + b
  }
}

Property Decorators ​

typescript
// Property decorator
function format(formatString: string) {
  return function (target: any, propertyKey: string) {
    let value: string

    const getter = () => value
    const setter = (newVal: string) => {
      value = formatString.replace('%s', newVal)
    }

    Object.defineProperty(target, propertyKey, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true
    })
  }
}

class Greeter {
  @format('Hello, %s!')
  greeting: string

  constructor(message: string) {
    this.greeting = message
  }
}

const greeter = new Greeter('world')
console.log(greeter.greeting)  // "Hello, world!"

Module System ​

Namespaces ​

typescript
// Namespace
namespace Validation {
  export interface StringValidator {
    isAcceptable(s: string): boolean
  }

  const lettersRegexp = /^[A-Za-z]+$/
  const numberRegexp = /^[0-9]+$/

  export class LettersOnlyValidator implements StringValidator {
    isAcceptable(s: string) {
      return lettersRegexp.test(s)
    }
  }

  export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
      return s.length === 5 && numberRegexp.test(s)
    }
  }
}

// Using namespaces
const validators: { [s: string]: Validation.StringValidator } = {}
validators['ZIP code'] = new Validation.ZipCodeValidator()
validators['Letters only'] = new Validation.LettersOnlyValidator()

Module Declarations ​

typescript
// Declare external modules
declare module 'lodash' {
  export function chunk<T>(array: T[], size?: number): T[][]
  export function debounce<T extends (...args: any[]) => any>(
    func: T,
    wait?: number,
    options?: object
  ): T
}

// Global declarations
declare global {
  interface Window {
    myGlobalFunction: (name: string) => void
  }
}

// Module augmentation
declare module 'vue' {
  interface ComponentCustomProperties {
    $myGlobalProperty: string
  }
}

Practical Tips ​

Type Guards ​

typescript
// User-defined type guards
function isString(value: any): value is string {
  return typeof value === 'string'
}

function isNumber(value: any): value is number {
  return typeof value === 'number'
}

// Using type guards
function processValue(value: string | number) {
  if (isString(value)) {
    // value type is narrowed to string
    console.log(value.toUpperCase())
  } else if (isNumber(value)) {
    // value type is narrowed to number
    console.log(value.toFixed(2))
  }
}

// Discriminated unions
interface Bird {
  type: 'bird'
  flyingSpeed: number
}

interface Horse {
  type: 'horse'
  runningSpeed: number
}

type Animal = Bird | Horse

function moveAnimal(animal: Animal) {
  switch (animal.type) {
    case 'bird':
      console.log('Flying at speed: ' + animal.flyingSpeed)
      break
    case 'horse':
      console.log('Running at speed: ' + animal.runningSpeed)
      break
  }
}

Index Signatures and Mapped Types ​

typescript
// Index signatures
interface StringDictionary {
  [key: string]: string
}

interface NumberDictionary {
  [key: string]: number
  length: number    // ok, length is a number
  name: string      // error, name's type doesn't match the index signature
}

// Advanced mapped types
type Pick<T, K extends keyof T> = {
  [P in K]: T[P]
}

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>

interface User {
  id: number
  name: string
  email: string
  password: string
}

type PublicUser = Omit<User, 'password'>  // { id: number; name: string; email: string }
type UserCredentials = Pick<User, 'email' | 'password'>  // { email: string; password: string }

Function Overloads ​

typescript
// Function overloads
function reverse(x: string): string
function reverse(x: number): number
function reverse(x: boolean): boolean
function reverse(x: string | number | boolean): string | number | boolean {
  if (typeof x === 'string') {
    return x.split('').reverse().join('')
  } else if (typeof x === 'number') {
    return Number(x.toString().split('').reverse().join(''))
  } else {
    return !x
  }
}

const reversedString = reverse('hello')    // string
const reversedNumber = reverse(12345)      // number
const reversedBoolean = reverse(true)      // boolean

Real-world Project: Type-safe API Client ​

Let's create a type-safe API client:

typescript
// types/api.ts
export interface User {
  id: number
  name: string
  email: string
  createdAt: string
}

export interface CreateUserRequest {
  name: string
  email: string
}

export interface UpdateUserRequest {
  name?: string
  email?: string
}

export interface ApiResponse<T> {
  data: T
  message: string
  success: boolean
}

export interface PaginatedResponse<T> {
  data: T[]
  total: number
  page: number
  limit: number
}

// api/client.ts
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'

interface RequestConfig {
  method: HttpMethod
  headers?: Record<string, string>
  body?: any
}

class ApiClient {
  private baseURL: string
  private defaultHeaders: Record<string, string>

  constructor(baseURL: string) {
    this.baseURL = baseURL
    this.defaultHeaders = {
      'Content-Type': 'application/json'
    }
  }

  private async request<T>(
    endpoint: string, 
    config: RequestConfig
  ): Promise<ApiResponse<T>> {
    const url = `${this.baseURL}${endpoint}`
    const headers = { ...this.defaultHeaders, ...config.headers }

    const response = await fetch(url, {
      method: config.method,
      headers,
      body: config.body ? JSON.stringify(config.body) : undefined
    })

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }

    return response.json()
  }

  async get<T>(endpoint: string): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, { method: 'GET' })
  }

  async post<T, U = any>(
    endpoint: string, 
    data: U
  ): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, {
      method: 'POST',
      body: data
    })
  }

  async put<T, U = any>(
    endpoint: string, 
    data: U
  ): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, {
      method: 'PUT',
      body: data
    })
  }

  async delete<T>(endpoint: string): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, { method: 'DELETE' })
  }
}

// api/users.ts
export class UserService {
  constructor(private client: ApiClient) {}

  async getUsers(): Promise<ApiResponse<PaginatedResponse<User>>> {
    return this.client.get<PaginatedResponse<User>>('/users')
  }

  async getUser(id: number): Promise<ApiResponse<User>> {
    return this.client.get<User>(`/users/${id}`)
  }

  async createUser(data: CreateUserRequest): Promise<ApiResponse<User>> {
    return this.client.post<User, CreateUserRequest>('/users', data)
  }

  async updateUser(
    id: number, 
    data: UpdateUserRequest
  ): Promise<ApiResponse<User>> {
    return this.client.put<User, UpdateUserRequest>(`/users/${id}`, data)
  }

  async deleteUser(id: number): Promise<ApiResponse<void>> {
    return this.client.delete<void>(`/users/${id}`)
  }
}

// Usage example
const apiClient = new ApiClient('https://api.example.com')
const userService = new UserService(apiClient)

async function example() {
  try {
    // Get user list
    const usersResponse = await userService.getUsers()
    console.log(usersResponse.data.data) // User[]

    // Create new user
    const newUser = await userService.createUser({
      name: 'John Doe',
      email: 'john@example.com'
    })
    console.log(newUser.data) // User

    // Update user
    const updatedUser = await userService.updateUser(1, {
      name: 'Jane Doe'
    })
    console.log(updatedUser.data) // User

  } catch (error) {
    console.error('API Error:', error)
  }
}

Performance Optimization ​

Type Inference Optimization ​

typescript
// Avoid excessive annotations
// ❌ Bad
const users: User[] = await getUsers()

// ✅ Good
const users = await getUsers() // TypeScript can infer the type

// Use const assertions
// ❌ Bad
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
} // Type is { apiUrl: string; timeout: number }

// ✅ Good
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
} as const // Type is { readonly apiUrl: "https://api.example.com"; readonly timeout: 5000 }

Compilation Optimization ​

json
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "node",
    "strict": true,
    "skipLibCheck": true,
    "incremental": true,
    "tsBuildInfoFile": ".tsbuildinfo"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Testing ​

Type Testing ​

typescript
// Type testing utilities
type Expect<T extends true> = T
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
  ? true
  : false

// Test cases
type cases = [
  Expect<Equal<Pick<User, 'name'>, { name: string }>>,
  Expect<Equal<Omit<User, 'id'>, { name: string; email: string; createdAt: string }>>,
]

// Runtime testing
import { describe, it, expect } from 'vitest'

describe('UserService', () => {
  it('should create user with correct type', async () => {
    const userService = new UserService(apiClient)
    const userData: CreateUserRequest = {
      name: 'Test User',
      email: 'test@example.com'
    }
    
    const response = await userService.createUser(userData)
    
    expect(response.data).toHaveProperty('id')
    expect(response.data).toHaveProperty('name', userData.name)
    expect(response.data).toHaveProperty('email', userData.email)
  })
})

Best Practices ​

1. Type Design Principles ​

typescript
// ✅ Use union types instead of enums
type Theme = 'light' | 'dark' | 'auto'

// ✅ Use interface extension
interface BaseEntity {
  id: string
  createdAt: Date
  updatedAt: Date
}

interface User extends BaseEntity {
  name: string
  email: string
}

// ✅ Use generic constraints
function processItems<T extends { id: string }>(items: T[]): T[] {
  return items.filter(item => item.id.length > 0)
}

2. Error Handling ​

typescript
// Result type pattern
type Result<T, E = Error> = 
  | { success: true; data: T }
  | { success: false; error: E }

async function safeApiCall<T>(
  apiCall: () => Promise<T>
): Promise<Result<T>> {
  try {
    const data = await apiCall()
    return { success: true, data }
  } catch (error) {
    return { 
      success: false, 
      error: error instanceof Error ? error : new Error(String(error))
    }
  }
}

// Usage
const result = await safeApiCall(() => userService.getUser(1))
if (result.success) {
  console.log(result.data) // User
} else {
  console.error(result.error) // Error
}

3. Configuration Management ​

typescript
// Environment configuration
interface Config {
  readonly apiUrl: string
  readonly timeout: number
  readonly retries: number
  readonly debug: boolean
}

const createConfig = (env: string): Config => {
  const baseConfig = {
    timeout: 5000,
    retries: 3,
    debug: false
  }

  switch (env) {
    case 'development':
      return {
        ...baseConfig,
        apiUrl: 'http://localhost:3000',
        debug: true
      }
    case 'production':
      return {
        ...baseConfig,
        apiUrl: 'https://api.production.com'
      }
    default:
      throw new Error(`Unknown environment: ${env}`)
  }
}

export const config = createConfig(process.env.NODE_ENV || 'development')

Summary ​

Advanced TypeScript features allow us to:

  1. Type Safety - Catch errors at compile time
  2. Code Completion - Better development experience
  3. Refactoring Support - Safely refactor code
  4. Documentation - Types serve as documentation
  5. Team Collaboration - Unified code standards

Next Steps ​

  1. TypeScript with React
  2. TypeScript with Node.js
  3. TypeScript Compiler API
  4. TypeScript Performance Optimization

References ​


Master these advanced TypeScript features to write safer, more elegant code! 🚀

VitePress Development Guide