S SmartDocs
Série: Typescript typescript 227 lignes · Mis à jour 2025-12-27

10-union-intersection.ts

Typescript/src/01-beginner/10-union-intersection.ts

// ============================================
// 10. Union and Intersection Types
// ============================================

console.log("=== UNION AND INTERSECTION TYPES ===\n");

// Union Types - value can be one of several types
console.log("=== UNION TYPES ===\n");
console.log("Union types allow a value to be one of several types");
console.log("Syntax: type A = Type1 | Type2 | Type3\n");

type StringOrNumber = string | number;

function processValue(value: StringOrNumber) {
  // value can be either string or number
  if (typeof value === "string") {
    return value.toUpperCase();
  } else {
    return value.toFixed(2);
  }
}

console.log("Example: StringOrNumber = string | number");
console.log(`  processValue("hello") → "${processValue("hello")}"`);
console.log(`  processValue(42) → "${processValue(42)}"`);
console.log("  ✅ Value can be either string OR number");

// Common Union Patterns
console.log("\n=== COMMON UNION PATTERNS ===\n");

type ID = string | number;
console.log("1. ID type: string | number");
const userId: ID = "123";
const numericId: ID = 456;
console.log(`   userId: ${userId} (string)`);
console.log(`   numericId: ${numericId} (number)`);

type MaybeString = string | null | undefined;
console.log("\n2. MaybeString: string | null | undefined");
const maybe1: MaybeString = "hello";
const maybe2: MaybeString = null;
const maybe3: MaybeString = undefined;
console.log(`   maybe1: ${maybe1}`);
console.log(`   maybe2: ${maybe2}`);
console.log(`   maybe3: ${maybe3}`);

type Status = "pending" | "approved" | "rejected";
console.log("\n3. Status: Literal union type");
const status1: Status = "pending";
const status2: Status = "approved";
console.log(`   status1: "${status1}"`);
console.log(`   status2: "${status2}"`);
console.log("   ✅ Can only be one of: 'pending' | 'approved' | 'rejected'");

type Result = 
  | { success: true; data: any } 
  | { success: false; error: string };

console.log("\n4. Result: Discriminated union");
const successResult: Result = { success: true, data: { id: 1, name: "Alice" } };
const errorResult: Result = { success: false, error: "Not found" };
console.log(`   successResult:`, successResult);
console.log(`   errorResult:`, errorResult);
console.log("   ✅ Type-safe error handling pattern");

// Discriminated Unions
console.log("\n=== DISCRIMINATED UNIONS ===\n");

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rectangle"; width: number; height: number }
  | { kind: "triangle"; base: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "rectangle":
      return shape.width * shape.height;
    case "triangle":
      return (shape.base * shape.height) / 2;
  }
}

console.log("Discriminated unions use a common property to distinguish types:");
const circle: Shape = { kind: "circle", radius: 5 };
const rectangle: Shape = { kind: "rectangle", width: 4, height: 6 };
const triangle: Shape = { kind: "triangle", base: 3, height: 4 };

console.log(`  Circle (radius: 5): area = ${area(circle).toFixed(2)}`);
console.log(`  Rectangle (4x6): area = ${area(rectangle)}`);
console.log(`  Triangle (base: 3, height: 4): area = ${area(triangle)}`);
console.log("  ✅ TypeScript narrows the type based on 'kind' property");

// Intersection Types - value must satisfy all types
console.log("\n=== INTERSECTION TYPES ===\n");

interface Person {
  name: string;
  age: number;
}

interface Employee {
  employeeId: string;
  department: string;
}

type Staff = Person & Employee;

const staff: Staff = {
  name: "Alice",
  age: 30,
  employeeId: "E001",
  department: "Engineering"
  // Must have all properties from both interfaces
};

console.log("Intersection types combine multiple types:");
console.log("  Staff = Person & Employee");
console.log("  ✅ Must have ALL properties from BOTH types");
console.log(`  staff:`, staff);
console.log("  Has properties from Person (name, age) AND Employee (employeeId, department)");

// Merging Types
console.log("\n=== MERGING TYPES WITH INTERSECTION ===\n");

type A = { a: number };
type B = { b: string };
type C = A & B; // { a: number; b: string }

const merged: C = { a: 42, b: "hello" };
console.log("Merging types with intersection:");
console.log(`  type A = { a: number }`);
console.log(`  type B = { b: string }`);
console.log(`  type C = A & B → { a: number; b: string }`);
console.log(`  merged:`, merged);

// Type Narrowing
console.log("\n=== TYPE NARROWING ===\n");

function process(value: string | number) {
  if (typeof value === "string") {
    // TypeScript knows value is string here
    return value.length;
  } else {
    // TypeScript knows value is number here
    return value.toFixed(2);
  }
}

console.log("TypeScript narrows types based on control flow:");
console.log(`  process("hello") → ${process("hello")} (string length)`);
console.log(`  process(42) → "${process(42)}" (number formatted)`);
console.log("  ✅ TypeScript knows the type in each branch");

// Type Guards
console.log("\n=== TYPE GUARDS ===\n");

interface Dog {
  type: "dog";
  bark(): void;
}

interface Cat {
  type: "cat";
  meow(): void;
}

type Pet = Dog | Cat;

function isDog(pet: Pet): pet is Dog {
  return pet.type === "dog";
}

function makeSound(pet: Pet) {
  if (isDog(pet)) {
    pet.bark(); // TypeScript knows it's a Dog
  } else {
    pet.meow(); // TypeScript knows it's a Cat
  }
}

const myDog: Pet = {
  type: "dog",
  bark() {
    console.log("    Woof! Woof!");
  }
};

const myCat: Pet = {
  type: "cat",
  meow() {
    console.log("    Meow!");
  }
};

console.log("Type guards narrow union types:");
console.log("  Pet = Dog | Cat");
console.log("  isDog(pet): pet is Dog → type predicate");
console.log("\n  Calling makeSound with dog:");
makeSound(myDog);
console.log("\n  Calling makeSound with cat:");
makeSound(myCat);
console.log("  ✅ TypeScript knows the exact type after type guard");

// Combining Union and Intersection
console.log("\n=== COMBINING UNION AND INTERSECTION ===\n");

type A2 = { a: number } | { b: string };
type B2 = { c: boolean } & A2;
// B2 is: ({ c: boolean; a: number }) | ({ c: boolean; b: string })

const combined1: B2 = { c: true, a: 42 };
const combined2: B2 = { c: false, b: "hello" };

console.log("Combining union and intersection:");
console.log(`  type A2 = { a: number } | { b: string }`);
console.log(`  type B2 = { c: boolean } & A2`);
console.log(`  Result: ({ c: boolean; a: number }) | ({ c: boolean; b: string })`);
console.log(`  combined1:`, combined1);
console.log(`  combined2:`, combined2);
console.log("  ✅ Intersection distributes over union");

console.log("\n=== DEMO COMPLETE ===");

export { processValue, area, staff, makeSound };

Articles liés