Peeps Avatar

Hello, codestus.

Go back

Lọc mảng trả về chính xác kiểu dữ liệu trong TypeScript

Published at: 27/06/2024

6 mins read

Lọc mảng trong TypeScript trong khi duy trì các kiểu dữ liệu chính xác có thể là một thử thách nhưng rất cần thiết cho sự phát triển ứng dụng và bảo trì. Trong bài viết này, chúng ta sẽ khám phá cách tạo một type guard để đảm bảo rằng mảng lọc chỉ chứa loại dữ liệu mong muốn. Chúng ta cũng sẽ thảo luận về nhược điểm của type guards và so sánh chúng với các hàm assertion. Cuối cùng, bạn sẽ có thể lọc mảng đúng cách trong TypeScript, trả về các giá trị và kiểu dữ liệu tương ứng.

Demo

Giả sử chúng ta có một kiểu gọi là ResponseData chứa một thuộc tính dữ liệu kiểu string. Tiếp theo, chúng ta sẽ tạo một mảng các item, bao gồm một số đối tượng ResponseData. Chúng ta sẽ tạo một đối tượng với dữ liệu "Banana" và một đối tượng khác với dữ liệu "Dog". Để tạo một tập hợp không đồng nhất, chúng ta cũng sẽ thêm một số giá trị undefined vào mảng, như sau:

type ResponseData = {
  data: string;
};

const items = [{ data: 'Banana' }, undefined, { data: 'Dog' }, undefined];

Với các giá trị demo đã được thiết lập, typeScript biết rằng mảng của chúng ta là một union của các giá trị ResponseData và các giá trị undefined:

type ResponseData = {
  data: string;
};

const items: (ResponseData | undefined)[] = [{ data: 'Banana' }, undefined, { data: 'Dog' }, undefined];

Lọc các items trong TypeScript

Tiếp theo, chúng ta cần lọc các items chỉ để trả về những cái đã được định nghĩa. Chúng ta sẽ triển khai một bộ lọc kiểm tra các giá trị không phải là undefined. Bộ lọc sẽ loại bỏ các items mà điều kiện đánh giá là false:

type ResponseData = {
  data: string;
};

const items: (ResponseData | undefined)[] = [{ data: 'Banana' }, undefined, { data: 'Dog' }, undefined];

const payloads = items.filter((item) => item !== undefined);

console.log(payloads);

Thoạt nhìn, mọi thứ có vẻ ổn. Tuy nhiên, khi kiểm tra kỹ hơn, IDE của chúng ta vẫn coi payloads là loại ResponseData hoặc undefined. Lý tưởng nhất, chúng ta muốn bộ lọc của mình trả về đúng kiểu dữ liệu được suy luận từ hàm .filter mà chúng ta đã định nghĩa.

Lọc các items với một type guard

Chúng ta có thể đạt được điều này bằng cách chuyển bộ lọc của mình thành một type guard. Để làm điều này, chúng ta chỉ cần thêm một type predicate vào hàm filter của mình bằng cách sử dụng cú pháp item is ResponseData. Điều chỉnh nhỏ này sẽ đảm bảo rằng chúng ta có được loại phản hồi mong muốn và cung cấp hỗ trợ tự động hoàn thiện tốt hơn trong IDE của chúng ta:

type ResponseData = {
  data: string;
};

const items: (ResponseData | undefined)[] = [{ data: 'Banana' }, undefined, { data: 'Dog' }, undefined];

const payloads = items.filter((item): item is ResponseData => item !== undefined);

console.log(payloads);

Đảm bảo type guards có khả năng tái sử dụng

Để làm cho nó rõ ràng hơn, chúng ta có thể trích xuất type guard của mình vào một hàm riêng biệt. Hãy tạo một hàm gọi là isResponseData nhận một item làm tham số đầu vào. Item có thể là loại union, bao gồm ResponseData hoặc undefined.

Kiểu trả về sẽ là một type predicate cho biết rằng item của chúng ta là loại ResponseData. Type guard của chúng ta sẽ đánh giá là true nếu item đầu vào không phải undefined. Sau khi thiết lập type guard, chúng ta có thể cung cấp nó cho hàm filter của mảng:

type ResponseData = {
  data: string;
};

function isResponseData(item: ResponseData | undefined): item is ResponseData {
  return item !== undefined;
}

const items: (ResponseData | undefined)[] = [{ data: 'Banana' }, undefined, { data: 'Dog' }, undefined];

const payloads = items.filter(isResponseData);

console.log(payloads);

Nhược điểm của type guards

Có thể giả định các kiểu không hợp lệ, chẳng hạn chúng ta có thể nói rằng các items là undefined trong trường hợp chúng không phải như vậy. Những trường hợp như vậy, type predicate sẽ ghi đè trình biên dịch TypeScript, dẫn đến phản hồi rằng các payloads của chúng ta là undefined trong thời gian phát triển:

type ResponseData = {
  data: string;
};

function isResponseData(item: ResponseData | undefined): asserts item is undefined {
  return item !== undefined;
}

const items: (ResponseData | undefined)[] = [{ data: 'Banana' }, undefined, { data: 'Dog' }, undefined];

const payloads = items.filter(isResponseData);

console.log(payloads);

Assertion Functions

Bằng cách sử dụng từ khóa asserts, chúng ta có thể biến type guard của mình thành một assertion function. Cách tiếp cận này yêu cầu một số điều chỉnh đối với việc triển khai type guard của chúng ta. Thay vì trả về một giá trị boolean, chúng ta sẽ ném ra một lỗi nếu có điều gì đó bất ngờ xảy ra. Nếu item đầu vào đáp ứng điều kiện, trình biên dịch TypeScript sẽ giả định rằng nó có kiểu ResponseData dựa trên assertion của chúng ta:

type ResponseData = {
  data: string;
};

function isResponseData(item: ResponseData | undefined): asserts item is ResponseData {
  if (item === undefined) {
    throw new Error('It is undefined');
  }
}

const items: (ResponseData | undefined)[] = [{ data: 'Banana' }, undefined, { data: 'Dog' }, undefined];

const payloads = items.filter(isResponseData);

console.log(payloads);

Type Guards vs. Assertion Functions

Khi quyết định giữa một type guard hay assertion function, hãy ghi nhớ những điều sau: Assertion functions phù hợp hơn cho các điều kiện cần từ chối đầu vào tại runtime, trong khi type guards rất tốt để thu hẹp một kiểu trong thời gian phát triển.