Menu

Nạp chồng hàm trong TypeScript: overload signature

Nạp chồng hàm (function overload) trong TypeScript cho phép một hàm có nhiều chữ ký gọi, mỗi chữ ký có kiểu trả về riêng. Tìm hiểu mẫu overload signature kèm phần cài đặt, các quy tắc trình biên dịch kiểm tra, khi nào tham số union tốt hơn, và overload trong lớp.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Nạp chồng hàm (function overloading) trong TypeScript là viết nhiều chữ ký gọi cho một hàm, theo sau là một phần cài đặt duy nhất. Mỗi chữ ký có thể ghép các kiểu tham số khác nhau với một kiểu trả về khác nhau, và nơi gọi nhận được đúng kiểu chính xác.

Nếu không có các overload, parse sẽ trả về number | number[] cho mọi lời gọi, và one + 1 sẽ là lỗi cho đến khi bạn tự thu hẹp kết quả.

Overload signature và phần cài đặt

Một hàm được nạp chồng có hai phần:

  1. Overload signature: các khai báo không có thân hàm, mỗi khai báo cho một dạng lời gọi được hỗ trợ. Đây là những chữ ký duy nhất mà nơi gọi dùng được.
  2. Chữ ký cài đặt: khai báo cuối cùng, có thân hàm. Tham số của nó phải nhận được mọi thứ các overload nhận, và kiểu trả về của nó phải bao quát kiểu trả về của mọi overload. Từ bên ngoài không nhìn thấy nó.

Kiểu chỉ tồn tại lúc biên dịch, nên lúc chạy chỉ có một hàm JavaScript. Phần cài đặt phải tự xem xét các đối số (typeof, Array.isArray, arguments.length...) để quyết định làm gì. Trình biên dịch kiểm tra rằng các overload và phần cài đặt khớp với nhau:

function format(value: string): string;
function format(value: number): number {
  return value;
}
// error TS2394: This overload signature is not compatible with its implementation signature.

Cách sửa là mở rộng phần cài đặt: function format(value: string | number): string | number.

Không gọi được chữ ký cài đặt

Đây là quy tắc khiến nhiều người bất ngờ nhất. Một lời gọi phải tự khớp với một trong các overload signature; TypeScript không kết hợp chúng lại.

Trình biên dịch in ra:

index.ts(12,19): error TS2769: No overload matches this call.
  The last overload gave the following error.
    Argument of type 'string | string[]' is not assignable to parameter of type 'string[]'.
      Type 'string' is not assignable to type 'string[]'.

Phần cài đặt nhận string | string[], nhưng nơi gọi không thấy được nó. Thêm một overload thứ ba nhận union và trả về union, thế là lời gọi biên dịch được và in ra [ 1, 2 ]:

function parse(input: string): number;
function parse(input: string[]): number[];
function parse(input: string | string[]): number | number[];
function parse(input: string | string[]): number | number[] {
  return Array.isArray(input) ? input.map(Number) : Number(input);
}

Số lượng tham số khác nhau

Overload cũng mô tả được các lời gọi với số lượng tham số khác nhau. Ở đây một ngày có thể được tạo từ timestamp hoặc từ năm, tháng và ngày, nhưng không thể từ hai số:

Một chữ ký duy nhất với hai tham số không bắt buộc sẽ nhận makeDate(2024, 3) và âm thầm tạo ra một ngày sai. Các overload biến điều đó thành lỗi biên dịch (TS2575).

Thứ tự rất quan trọng

TypeScript thử các overload từ trên xuống dưới và chọn overload đầu tiên khớp. Hãy đặt các chữ ký cụ thể nhất lên trước. Một overload quá rộng nằm sớm trong danh sách sẽ nuốt mất những lời gọi dành cho các overload phía sau:

function describe(value: unknown): string;   // matches everything
function describe(value: string): "text";    // never chosen
function describe(value: unknown): string {
  return typeof value === "string" ? "text" : "other";
}

const d = describe("hi"); // d: string, not "text"

Đổi chỗ hai chữ ký đầu tiên thì describe("hi") có kiểu "text".

Overload hay tham số union?

Overload đáng mấy dòng code thêm khi kiểu trả về phụ thuộc vào kiểu của đối số. Khi không phải vậy, một chữ ký với tham số union sẽ ngắn hơn, dễ đọc hơn, và nhận được các đối số union mà overload sẽ từ chối.

DùngKhi
Tham số unionKiểu trả về giống nhau với mọi đầu vào
Tham số không bắt buộcCác dạng lời gọi chỉ khác nhau ở những đối số cuối có thể bỏ qua tùy ý
OverloadKiểu trả về thay đổi theo đối số, hoặc phải từ chối một số tổ hợp đối số
GenericKiểu trả về được dựng từ kiểu của đối số, như identity<T>(x: T): T

Một generic kết hợp với conditional type có thể diễn đạt một số bộ overload bằng một chữ ký, nhưng với hai hay ba trường hợp thì overload thường dễ đọc hơn.

Nạp chồng phương thức và hàm khởi tạo

Phương thức dùng cùng mẫu này bên trong lớp: các overload signature, rồi đến phương thức có thân. Hàm khởi tạo cũng nạp chồng được theo cách tương tự.

Interface và kiểu đối tượng cũng khai báo được overload, dưới dạng nhiều chữ ký gọi hoặc nhiều chữ ký phương thức trùng tên. Nhiều hàm có sẵn được khai báo theo cách này: di chuột lên reduce của một mảng trong trình soạn thảo sẽ thấy "+2 overloads".

Câu hỏi thường gặp

TypeScript có hỗ trợ nạp chồng hàm không?

Có, ở mức kiểu. Bạn viết nhiều overload signature (các khai báo không có thân hàm) rồi đến một phần cài đặt. Nơi gọi chỉ thấy các overload signature. Lúc chạy vẫn chỉ có một hàm JavaScript, nên phần cài đặt tự kiểm tra các đối số và xử lý mọi trường hợp.

"No overload matches this call" nghĩa là gì?

Lỗi TS2769: các đối số không khớp với overload signature nào. Chữ ký cài đặt không được tính, nên một lời gọi với đối số union như string | string[] vẫn thất bại dù phần cài đặt nhận được nó. Hãy thêm một overload nhận union đó, hoặc thay các overload bằng một chữ ký duy nhất.

Khi nào nên dùng overload thay cho union type?

Dùng overload khi kiểu trả về phụ thuộc vào kiểu của đối số được truyền vào, ví dụ truyền string thì nhận number, còn truyền string[] thì nhận number[]. Khi kiểu trả về giống nhau với mọi đầu vào, một chữ ký duy nhất với tham số union sẽ đơn giản hơn và còn nhận được đối số union.

Có thể nạp chồng arrow function trong TypeScript không?

Không thể bằng cú pháp khai báo overload, vốn chỉ dùng được cho khai báo function và phương thức. Bạn có thể gán cho một biến kiểu được nạp chồng với nhiều chữ ký gọi, type Parse = { (s: string): number; (s: string[]): number[] }, nhưng gán một arrow function vào đó thường cần ép kiểu, nên khai báo function là lựa chọn gọn hơn.

Vì sao overload signature không tương thích với chữ ký cài đặt?

Lỗi TS2394 nghĩa là một overload nhận hoặc trả về thứ mà phần cài đặt không có. Tham số của phần cài đặt phải nhận được tham số của mọi overload, và kiểu trả về của nó phải tương thích với kiểu trả về của mọi overload. Mở rộng phần cài đặt (thường thành một union) sẽ sửa được lỗi.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu