Hàm (function) trong TypeScript là một hàm JavaScript có kiểu cho các tham số và, nếu muốn, cho giá trị trả về. Mỗi tham số có một chú thích kiểu, kiểu trả về đặt sau danh sách tham số, và trình biên dịch kiểm tra mọi lời gọi theo cả hai.
Cả hai lời gọi sai đều là lỗi lúc biên dịch (TS2345 và TS2554). Các chú thích // @ts-expect-error cho trình biên dịch biết dòng tiếp theo dự kiến có lỗi, nên phần còn lại của tệp vẫn chạy. Xóa một trong hai rồi chạy lại để thấy thông báo lỗi thật.
Kiểu của tham số
Mỗi tham số được viết là name: Type. Với strict, vốn được bật mặc định trong TypeScript 7, một tham số không có chú thích kiểu và không có ngữ cảnh để suy ra sẽ gây lỗi: Parameter 'x' implicitly has an 'any' type. (TS7006). Vì vậy trên thực tế mọi tham số của một hàm đứng riêng đều được chú thích kiểu.
Kiểu nào cũng dùng được làm kiểu tham số: kiểu nguyên thủy, mảng, kiểu đối tượng, union, các hàm khác.
TypeScript kiểm tra cả số lượng đối số. Truyền nhiều hơn hoặc ít hơn số tham số hàm khai báo là lỗi, khác với JavaScript thuần vốn điền undefined cho đối số thiếu và bỏ qua đối số thừa. Để một tham số trở thành không bắt buộc hoặc có giá trị mặc định, xem tham số không bắt buộc.
Kiểu trả về: chú thích hay để suy ra
Kiểu trả về đứng sau dấu đóng ngoặc: function f(): Type. Nó không bắt buộc. Khi không có, TypeScript suy ra kiểu từ mọi return trong thân hàm.
Chú thích kiểu đáng giá khi hàm được export, khi hàm có nhiều nhánh trả về, hoặc khi hàm đệ quy. Khi đó lỗi xuất hiện ngay bên trong hàm, tại return sai, thay vì ở nơi gọi. Một trường hợp kinh điển là một nhánh quên trả về:
Trình biên dịch báo index.ts(2,32): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. Thêm một return "C"; ở cuối là chạy được. Nếu không có chú thích : string, hàm vẫn biên dịch được với kiểu suy ra "A" | "B" | undefined, và vấn đề sẽ chuyển sang người dùng kết quả.
void: hàm không trả về gì
Một hàm chỉ thực hiện việc gì đó (ghi log, ghi dữ liệu, thay đổi giá trị) có kiểu trả về void. Đó là kiểu TypeScript suy ra khi không có return nào kèm giá trị, và bạn cũng có thể viết rõ ra.
Những quy tắc sau về void khiến nhiều người bất ngờ:
| Tình huống | Được phép? |
|---|---|
function f(): void { return 42; } | Không: Type 'number' is not assignable to type 'void'. (TS2322) |
const f: () => void = () => 42; | Có: giá trị vẫn được trả về nhưng nơi gọi không được dựa vào nó |
function f(): undefined {} | Có (TypeScript 5.1 trở lên) |
Dùng kết quả của một hàm void trong if | Không: An expression of type 'void' cannot be tested for truthiness. (TS1345) |
Dòng thứ hai là có chủ ý. Đó là lý do arr.forEach(x => list.push(x)) biên dịch được dù push trả về một số. void trong một kiểu hàm nghĩa là "hàm này trả về gì cũng được, không ai đọc nó". Với một hàm không bao giờ trả về (luôn ném lỗi hoặc lặp vô hạn), kiểu trả về là never.
Arrow function và biểu thức hàm
Arrow function và biểu thức hàm nhận cùng kiểu chú thích. Kiểu trả về đặt sau danh sách tham số, trước =>.
Nếu không có cặp ngoặc tròn, (x, y) => { x, y } là một khối lệnh không có return, và hàm trả về undefined. Các quy tắc lúc chạy của arrow function (không có this riêng, không có arguments) giống như trong JavaScript; xem arrow function trong JavaScript.
Khi một hàm được viết trực tiếp làm đối số, bạn thường không cần chú thích kiểu cho tham số của nó. TypeScript biết callback nhận được gì từ hàm mà nó được truyền vào (contextual typing): trong [1, 2].map(n => n * 2), n đã là number. Cách viết kiểu của một hàm như một giá trị, (n: number) => string, được trình bày trong kiểu hàm.
Trả về nhiều giá trị
Một hàm chỉ trả về một giá trị, nên hãy trả về một đối tượng hoặc một tuple rồi phân rã nó.
Kiểu trả về dạng tuple cần được chú thích. Nếu không, [a, b] được suy ra là string[]: phân rã vẫn chạy, nhưng kiểu không còn nói rằng có đúng hai phần tử.
Hàm async
Một hàm async luôn trả về một Promise. Chú thích kiểu trả về là Promise<T>, trong đó T là thứ hàm trả về bên trong.
async function loadScore(id: number): Promise<number> {
await new Promise((resolve) => setTimeout(resolve, 10));
return id * 10;
}
async function main(): Promise<void> {
const score = await loadScore(4); // score: number
console.log("score", score);
}
main();
Viết async function f(): number là lỗi: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<number>'? (TS1064).
Tham số this
JavaScript quyết định this lúc gọi hàm. TypeScript cho phép bạn khai báo this phải là gì bằng một tham số đầu tiên giả mang tên this. Nó bị xóa khỏi JavaScript đã biên dịch và nơi gọi không truyền nó.
interface Counter {
count: number;
}
function increment(this: Counter, by: number): void {
this.count += by;
}
const c = { count: 0, increment };
c.increment(2); // fine: this is c
increment(2);
// error TS2684: The 'this' context of type 'void' is not assignable to method's 'this' of type 'Counter'.
Không có tham số this, this bên trong một hàm đứng riêng là một any ngầm định và chế độ strict báo 'this' implicitly has type 'any' because it does not have a type annotation. (TS2683). Trong phương thức của lớp, this đã có kiểu là instance của lớp, nên tham số này chủ yếu hữu ích cho các hàm đứng riêng được gắn vào đối tượng và cho những callback mà thư viện gọi với một this cụ thể.
Câu hỏi thường gặp
Làm sao để chỉ định kiểu trả về của hàm trong TypeScript?
Viết dấu hai chấm và kiểu ngay sau danh sách tham số: function total(a: number, b: number): number { ... }. Với arrow function, kiểu trả về đặt ở cùng vị trí: const total = (a: number, b: number): number => a + b;. Nếu bỏ qua, TypeScript sẽ suy ra kiểu trả về từ các câu lệnh return.
Có nên luôn chú thích kiểu trả về trong TypeScript không?
Không bắt buộc, vì kiểu trả về được suy ra. Hãy chú thích cho các hàm được export và các hàm có nhiều nhánh return: chú thích ghi lại cam kết của hàm, và một return sai sẽ được báo ngay bên trong hàm thay vì ở một nơi gọi nào đó xa xôi.
void và undefined trong TypeScript khác nhau thế nào?
void nghĩa là "nơi gọi không nên dùng giá trị trả về". undefined là một kiểu giá trị cụ thể. Một hàm khai báo (): void không được trả về giá trị, nhưng một kiểu callback () => void vẫn nhận một hàm có trả về thứ gì đó, và kết quả đơn giản là bị bỏ qua. Dùng void cho hàm không trả về gì và chỉ dùng undefined khi nơi gọi thật sự so sánh kết quả với undefined.
Làm sao để trả về nhiều giá trị từ một hàm TypeScript?
Trả về một đối tượng ({ min: number; max: number }) hoặc một tuple ([number, number]) rồi phân rã nó ở nơi gọi. Đối tượng rõ ràng hơn khi các giá trị có ý nghĩa khác nhau; tuple dễ đọc với những cặp ngắn như [value, setValue].
Hàm async trong TypeScript có kiểu trả về là gì?
Luôn là một Promise. Một hàm async trả về number có kiểu trả về Promise<number>, và viết : number cho nó là lỗi biên dịch (TS1064). Một hàm không trả về gì có kiểu Promise<void>.