type-system-basics.ts raw

   1  /**
   2   * TypeScript Type System Basics
   3   *
   4   * This file demonstrates fundamental TypeScript concepts including:
   5   * - Primitive types
   6   * - Object types (interfaces, type aliases)
   7   * - Union and intersection types
   8   * - Type inference and narrowing
   9   * - Function types
  10   */
  11  
  12  // ============================================================================
  13  // Primitive Types
  14  // ============================================================================
  15  
  16  const message: string = 'Hello, TypeScript!'
  17  const count: number = 42
  18  const isActive: boolean = true
  19  const nothing: null = null
  20  const notDefined: undefined = undefined
  21  
  22  // ============================================================================
  23  // Object Types
  24  // ============================================================================
  25  
  26  // Interface definition
  27  interface User {
  28    id: string
  29    name: string
  30    email: string
  31    age?: number // Optional property
  32    readonly createdAt: Date // Readonly property
  33  }
  34  
  35  // Type alias definition
  36  type Product = {
  37    id: string
  38    name: string
  39    price: number
  40    category: string
  41  }
  42  
  43  // Creating objects
  44  const user: User = {
  45    id: '1',
  46    name: 'Alice',
  47    email: 'alice@example.com',
  48    createdAt: new Date(),
  49  }
  50  
  51  const product: Product = {
  52    id: 'p1',
  53    name: 'Laptop',
  54    price: 999,
  55    category: 'electronics',
  56  }
  57  
  58  // ============================================================================
  59  // Union Types
  60  // ============================================================================
  61  
  62  type Status = 'idle' | 'loading' | 'success' | 'error'
  63  type ID = string | number
  64  
  65  function formatId(id: ID): string {
  66    if (typeof id === 'string') {
  67      return id.toUpperCase()
  68    }
  69    return id.toString()
  70  }
  71  
  72  // Discriminated unions
  73  type ApiResponse =
  74    | { success: true; data: User }
  75    | { success: false; error: string }
  76  
  77  function handleResponse(response: ApiResponse) {
  78    if (response.success) {
  79      // TypeScript knows response.data exists here
  80      console.log(response.data.name)
  81    } else {
  82      // TypeScript knows response.error exists here
  83      console.error(response.error)
  84    }
  85  }
  86  
  87  // ============================================================================
  88  // Intersection Types
  89  // ============================================================================
  90  
  91  type Timestamped = {
  92    createdAt: Date
  93    updatedAt: Date
  94  }
  95  
  96  type TimestampedUser = User & Timestamped
  97  
  98  const timestampedUser: TimestampedUser = {
  99    id: '1',
 100    name: 'Bob',
 101    email: 'bob@example.com',
 102    createdAt: new Date(),
 103    updatedAt: new Date(),
 104  }
 105  
 106  // ============================================================================
 107  // Array Types
 108  // ============================================================================
 109  
 110  const numbers: number[] = [1, 2, 3, 4, 5]
 111  const strings: Array<string> = ['a', 'b', 'c']
 112  const users: User[] = [user, timestampedUser]
 113  
 114  // Readonly arrays
 115  const immutableNumbers: readonly number[] = [1, 2, 3]
 116  // immutableNumbers.push(4) // Error: push does not exist on readonly array
 117  
 118  // ============================================================================
 119  // Tuple Types
 120  // ============================================================================
 121  
 122  type Point = [number, number]
 123  type NamedPoint = [x: number, y: number, z?: number]
 124  
 125  const point: Point = [10, 20]
 126  const namedPoint: NamedPoint = [10, 20, 30]
 127  
 128  // ============================================================================
 129  // Function Types
 130  // ============================================================================
 131  
 132  // Function declaration
 133  function add(a: number, b: number): number {
 134    return a + b
 135  }
 136  
 137  // Arrow function
 138  const subtract = (a: number, b: number): number => a - b
 139  
 140  // Function type alias
 141  type MathOperation = (a: number, b: number) => number
 142  
 143  const multiply: MathOperation = (a, b) => a * b
 144  
 145  // Optional parameters
 146  function greet(name: string, greeting?: string): string {
 147    return `${greeting ?? 'Hello'}, ${name}!`
 148  }
 149  
 150  // Default parameters
 151  function createUser(name: string, role: string = 'user'): User {
 152    return {
 153      id: Math.random().toString(),
 154      name,
 155      email: `${name.toLowerCase()}@example.com`,
 156      createdAt: new Date(),
 157    }
 158  }
 159  
 160  // Rest parameters
 161  function sum(...numbers: number[]): number {
 162    return numbers.reduce((acc, n) => acc + n, 0)
 163  }
 164  
 165  // ============================================================================
 166  // Type Inference
 167  // ============================================================================
 168  
 169  // Type is inferred as string
 170  let inferredString = 'hello'
 171  
 172  // Type is inferred as number
 173  let inferredNumber = 42
 174  
 175  // Type is inferred as { name: string; age: number }
 176  let inferredObject = {
 177    name: 'Alice',
 178    age: 30,
 179  }
 180  
 181  // Return type is inferred as number
 182  function inferredReturn(a: number, b: number) {
 183    return a + b
 184  }
 185  
 186  // ============================================================================
 187  // Type Narrowing
 188  // ============================================================================
 189  
 190  // typeof guard
 191  function processValue(value: string | number) {
 192    if (typeof value === 'string') {
 193      // value is string here
 194      return value.toUpperCase()
 195    }
 196    // value is number here
 197    return value.toFixed(2)
 198  }
 199  
 200  // Truthiness narrowing
 201  function printName(name: string | null | undefined) {
 202    if (name) {
 203      // name is string here
 204      console.log(name.toUpperCase())
 205    }
 206  }
 207  
 208  // Equality narrowing
 209  function example(x: string | number, y: string | boolean) {
 210    if (x === y) {
 211      // x and y are both string here
 212      console.log(x.toUpperCase(), y.toLowerCase())
 213    }
 214  }
 215  
 216  // in operator narrowing
 217  type Fish = { swim: () => void }
 218  type Bird = { fly: () => void }
 219  
 220  function move(animal: Fish | Bird) {
 221    if ('swim' in animal) {
 222      // animal is Fish here
 223      animal.swim()
 224    } else {
 225      // animal is Bird here
 226      animal.fly()
 227    }
 228  }
 229  
 230  // instanceof narrowing
 231  function processError(error: Error | string) {
 232    if (error instanceof Error) {
 233      // error is Error here
 234      console.error(error.message)
 235    } else {
 236      // error is string here
 237      console.error(error)
 238    }
 239  }
 240  
 241  // ============================================================================
 242  // Type Predicates (Custom Type Guards)
 243  // ============================================================================
 244  
 245  function isUser(value: unknown): value is User {
 246    return (
 247      typeof value === 'object' &&
 248      value !== null &&
 249      'id' in value &&
 250      'name' in value &&
 251      'email' in value
 252    )
 253  }
 254  
 255  function processData(data: unknown) {
 256    if (isUser(data)) {
 257      // data is User here
 258      console.log(data.name)
 259    }
 260  }
 261  
 262  // ============================================================================
 263  // Const Assertions
 264  // ============================================================================
 265  
 266  // Without const assertion
 267  const mutableConfig = {
 268    host: 'localhost',
 269    port: 8080,
 270  }
 271  // mutableConfig.host = 'example.com' // OK
 272  
 273  // With const assertion
 274  const immutableConfig = {
 275    host: 'localhost',
 276    port: 8080,
 277  } as const
 278  // immutableConfig.host = 'example.com' // Error: cannot assign to readonly property
 279  
 280  // Array with const assertion
 281  const directions = ['north', 'south', 'east', 'west'] as const
 282  // Type: readonly ["north", "south", "east", "west"]
 283  
 284  // ============================================================================
 285  // Literal Types
 286  // ============================================================================
 287  
 288  type Direction = 'north' | 'south' | 'east' | 'west'
 289  type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
 290  type DiceValue = 1 | 2 | 3 | 4 | 5 | 6
 291  
 292  function move(direction: Direction, steps: number) {
 293    console.log(`Moving ${direction} by ${steps} steps`)
 294  }
 295  
 296  move('north', 10) // OK
 297  // move('up', 10) // Error: "up" is not assignable to Direction
 298  
 299  // ============================================================================
 300  // Index Signatures
 301  // ============================================================================
 302  
 303  interface StringMap {
 304    [key: string]: string
 305  }
 306  
 307  const translations: StringMap = {
 308    hello: 'Hola',
 309    goodbye: 'Adiós',
 310    thanks: 'Gracias',
 311  }
 312  
 313  // ============================================================================
 314  // Utility Functions
 315  // ============================================================================
 316  
 317  // Type-safe object keys
 318  function getObjectKeys<T extends object>(obj: T): Array<keyof T> {
 319    return Object.keys(obj) as Array<keyof T>
 320  }
 321  
 322  // Type-safe property access
 323  function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
 324    return obj[key]
 325  }
 326  
 327  const userName = getProperty(user, 'name') // Type: string
 328  const userAge = getProperty(user, 'age') // Type: number | undefined
 329  
 330  // ============================================================================
 331  // Named Return Values (Go-style)
 332  // ============================================================================
 333  
 334  function parseJSON(json: string): { data: unknown | null; err: Error | null } {
 335    let data: unknown | null = null
 336    let err: Error | null = null
 337  
 338    try {
 339      data = JSON.parse(json)
 340    } catch (error) {
 341      err = error instanceof Error ? error : new Error(String(error))
 342    }
 343  
 344    return { data, err }
 345  }
 346  
 347  // Usage
 348  const { data, err } = parseJSON('{"name": "Alice"}')
 349  if (err) {
 350    console.error('Failed to parse JSON:', err.message)
 351  } else {
 352    console.log('Parsed data:', data)
 353  }
 354  
 355  // ============================================================================
 356  // Exports
 357  // ============================================================================
 358  
 359  export type { User, Product, Status, ID, ApiResponse, TimestampedUser }
 360  export { formatId, handleResponse, processValue, isUser, getProperty, parseJSON }
 361  
 362