← TypeScript EnglishChapter 08 of 12

Union Types and Type Guards

## Learning Objectives - Create and use union types - Work with discriminated unions - Master type guards - Narrow types effectively - Handle null and undefined ## Union Types ### Basic Syntax ```typescript let id: string | number; id = "ABC123"; // OK id = 456; // OK // id = true; // Error ``` ### In Functions ```typescript function printId(id: string | number): void { console.log(`ID: ${id}`); } printId("ABC123"); // OK printId(456); // OK ``` ## Union Type Operations ### Limited Operations ```typescript function printId(id: string | number): void { // id.toUpperCase(); // Error: number has no toUpperCase if (typeof id === "string") { console.log(id.toUpperCase()); // OK within block } else { console.log(id.toFixed(2)); // OK within block } } ``` ## Discriminated Unions ### Common Property ```typescript interface Circle { kind: "circle"; radius: number; } interface Square { kind: "square"; side: number; } type Shape = Circle | Square; function area(shape: Shape): number { if (shape.kind === "circle") { return Math.PI * shape.radius ** 2; } else { return shape.side ** 2; } } ``` ### Example: API Responses ```typescript interface SuccessResponse { status: "success"; data: User[]; } interface ErrorResponse { status: "error"; message: string; } type ApiResponse = SuccessResponse | ErrorResponse; function handleResponse(response: ApiResponse): void { if (response.status === "success") { console.log(response.data); } else { console.error(response.message); } } ``` ## Type Guards ### typeof ```typescript function print(value: string | number): void { if (typeof value === "string") { console.log(value.toUpperCase()); } else { console.log(value.toFixed(2)); } } ``` ### instanceof ```typescript class Dog { bark(): void { console.log("Woof!"); } } class Cat { meow(): void { console.log("Meow!"); } } function speak(animal: Dog | Cat): void { if (animal instanceof Dog) { animal.bark(); } else { animal.meow(); } } ``` ### Property Check ```typescript interface Book { title: string; author: string; } interface Magazine { name: string; issue: number; } function print(item: Book | Magazine): void { if ("title" in item) { console.log(item.title, item.author); } else { console.log(item.name, item.issue); } } ``` ## Custom Type Guards ### Function Returns boolean ```typescript interface Person { name: string; age: number; } interface Animal { name: string; species: string; } function isPerson(obj: any): obj is Person { return "age" in obj && typeof obj.age === "number"; } function check(obj: Person | Animal): void { if (isPerson(obj)) { console.log(obj.age); } else { console.log(obj.species); } } ``` ### Type Predicate ```typescript function isString(value: unknown): value is string { return typeof value === "string"; } function process(value: unknown): void { if (isString(value)) { console.log(value.toUpperCase()); // TypeScript knows it's string } } ``` ## Null and Undefined ### Nullable Types ```typescript let name: string | null = null; name = "Alice"; // name = undefined; // Error if not in type ``` ### Optional Chaining ```typescript interface User { address?: { city: string; }; } let user: User = {}; // Old way let city = user && user.address && user.address.city; // Optional chaining let city2 = user?.address?.city; // undefined if missing ``` ### Nullish Coalescing ```typescript let name: string | null = null; // Default value when null/undefined let displayName = name ?? "Anonymous"; // vs || (also catches empty string) let empty = ""; let result = empty ?? "default"; // "" let result2 = empty || "default"; // "default" ``` ## Type Narrowing ### In else Block ```typescript function process(value: string | number | null): void { if (value === null) { console.log("null value"); } else { // TypeScript knows: string | number console.log(value); } } ``` ### After throw ```typescript function process(value: string | null): void { if (value === null) { throw new Error("Value is null"); } // value is string here console.log(value.toUpperCase()); } ``` ### Never Type After Exhaustion ```typescript function process(value: string | number): string { if (typeof value === "string") { return value.toUpperCase(); } // value is number here return value.toFixed(2); } ``` ## Intersection Types ### Intersection Types Syntax ```typescript interface A { a: string; } interface B { b: number; } type AB = A & B; let obj: AB = { a: "hello", b: 42 }; ``` ### Combine Unions ```typescript type UnionA = "a" | "b"; type UnionB = 1 | 2; type Combined = UnionA & UnionB; // Combined = "a" & 1 | "a" & 2 | "b" & 1 | "b" & 2 // = never (in most cases) ``` ## typeof for Variables ```typescript const config = { host: "localhost", port: 8080, debug: true }; type Config = typeof config; // { // host: string; // port: number; // debug: boolean; // } ``` ## Keyof Get keys as union type: ```typescript interface User { id: number; name: string; email: string; } type UserKeys = keyof User; // "id" | "name" | "email" function getProperty(obj: T, key: K): T[K] { return obj[key]; } let user: User = { id: 1, name: "Alice", email: "alice@example.com" }; getProperty(user, "name"); // string ``` ## Exhaustive Unions ### Never in Default ```typescript type Shape = Circle | Square | Triangle; function area(shape: Shape): number { switch (shape.kind) { case "circle": return Math.PI * shape.radius ** 2; case "square": return shape.side ** 2; case "triangle": return 0.5 * shape.base * shape.height; default: { // If we add a new shape, this catches it const _exhaustive: never = shape; return _exhaustive; } } } ``` ## Summary - Union types: `string | number` allows either - Discriminated unions use a common `kind` property - Type guards narrow types: `typeof`, `instanceof`, property checks - Custom guards: `obj is Type` return type - Optional chaining: `obj?.prop?.nested` - Nullish coalescing: `value ?? default` - Intersection types: `A & B` - Keyof gets union of object keys - Use `never` for exhaustive checking

Comments

Comments powered by Giscus

To enable comments, add your Giscus embed code here.

Learn more about Giscus →