Chuyển đến nội dung chính
eLearner.app
Mô-đun 2 · Bài học 2 trong tổng số 24/14 trong khóa học~15 min
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:

TS
let result: number | string;
result = 42; // Valid
result = 'Error 404'; // Valid

However, 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:

TS
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:

TS
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.

TS
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

tập thể dục#ts.m2.l2.e1
Nỗ lực: 0Đang tải…

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'.

Đang tải trình chỉnh sửa…
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

tập thể dục#ts.m2.l2.e2
Nỗ lực: 0Đang tải…

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.

Đang tải trình chỉnh sửa…
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'

tập thể dục#ts.m2.l2.e3
Nỗ lực: 0Đang tải…

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.

Đang tải trình chỉnh sửa…
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

tập thể dục#ts.m2.l2.e4
Nỗ lực: 0Đang tải…

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).

Đang tải trình chỉnh sửa…
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ử