Bài học theo mô-đun (2/2)
Công đoàn và thu hẹp
In the real world, variables and API responses do not always have a single fixed type. TypeScript offers Union Types to handle variability, and Type Narrowing to operate safely on them at runtime.
Union Types
A union type allows a variable to accept values of different types. It is expressed using the vertical bar (|) symbol:
let result: number | string;
result = 42; // Valid
result = 'Error 404'; // ValidHowever, when working with a union type, we cannot directly call methods that belong to only one of the types (for example, we cannot call .toUpperCase() if the variable can also be a number). We must first "narrow" the type.
Type Narrowing
Type Narrowing is the process where TypeScript analyzes control flow structures (such as if or switch) to deduce a more specific type for a variable at runtime.
There are several ways to perform narrowing:
1. The typeof Operator
Ideal for distinguishing primitive types:
function printLength(value: string | number) {
if (typeof value === 'string') {
// TypeScript knows 'value' is a string here
console.log(value.length);
} else {
// TypeScript knows 'value' is a number here
console.log(value.toFixed(2));
}
}2. The in Operator
Used to check if a specific property exists on an object:
interface Fish {
swim: () => void;
}
interface Bird {
fly: () => void;
}
function move(animal: Fish | Bird) {
if ('swim' in animal) {
animal.swim(); // Narrowing to Fish
} else {
animal.fly(); // Narrowing to Bird
}
}Discriminated Unions
The Discriminated Unions pattern consists of creating objects that share a common property with a unique literal value (called a discriminator). TypeScript recognizes this discriminator and automatically narrows the type within conditional blocks.
interface SuccessResponse {
status: 'success'; // Literal discriminator
data: string;
}
interface ErrorResponse {
status: 'error'; // Literal discriminator
errorMessage: string;
}
type ApiResponse = SuccessResponse | ErrorResponse;
function handleResponse(response: ApiResponse) {
if (response.status === 'success') {
console.log('Data received:', response.data);
} else {
console.error('An error occurred:', response.errorMessage);
}
}Try it yourself
Exercise 1: Union Types
Declare a variable named id that can be either a number or a string. Initialize it first with the number 101, then assign the string value 'USER-101'.
Hiển thị gợi ý
Use the | operator to join number and string in the let variable declaration.
Giải pháp khả dụng sau 3 lần thử
Exercise 2: Basic Type Narrowing
Create a function named formatInput that accepts a parameter input of type string or number. If input is a string, return input converted to uppercase. If it is a number, return input multiplied by 2. Specify types explicitly.
Hiển thị gợi ý
Use typeof input === 'string' inside an if block to branch the behavior.
Giải pháp khả dụng sau 3 lần thử
Exercise 3: Narrowing with 'in'
Given the two interfaces Car (with method drive) and Boat (with method sail), write a function named moveVehicle that accepts a parameter vehicle of type Car or Boat. If vehicle has the drive property, execute the drive() method. Otherwise, execute the sail() method.
Hiển thị gợi ý
Use the in operator in the form 'drive' in vehicle to narrow the interface.
Giải pháp khả dụng sau 3 lần thử
Exercise 4: Discriminated Union Shape
Define a type Shape which is the union of two types: Circle and Square. Circle has a kind property set to 'circle' (literal value) and a radius (number). Square has a kind property set to 'square' (literal value) and a side (number). Then write a function getArea that accepts shape of type Shape and returns the area as a number (for circle Math.PI * radius * radius, for square side * side).
Hiển thị gợi ý
Use shape.kind === 'circle' inside getArea to discriminate the type and calculate the correct area.
Giải pháp khả dụng sau 3 lần thử