Function trong TypeScript

Trong hướng dẫn này, bạn sẽ tìm hiểu về function (hàm) trong TypeScript và cách sử dụng chú thích kiểu để thực thi kiểm tra kiểu dữ liệu cho function.

Giới thiệu về function trong TypeScript

Function (hàm) trong TypeScript là các khối mã có thể đọc được, có thể bảo trì và có thể tái sử dụng.

Giống như JavaScript, bạn sử dụng từ khóa function để khai báo một function (hàm) trong TypeScript:

function name(parameter: type, parameter:type,...): returnType {
   // do something
}

Không giống như JavaScript, TypeScript cho phép bạn sử dụng chú thích kiểu trong các tham số và giá trị trả về của một function (hàm).

Hãy xem ví dụ về function add() sau:

function add(a: number, b: number): number {
    return a + b;
}

Trong ví dụ này, function add() chấp nhận hai tham số kiểu number.

Khi bạn gọi function add(), trình biên dịch TypeScript sẽ kiểm tra từng đối số được truyền vào hàm để đảm bảo rằng chúng là số.

Trong ví dụ về hàm add(), bạn chỉ có thể truyền các số vào nó, không thể truyền các giá trị của các kiểu khác.

Đoạn mã sau sẽ dẫn đến lỗi vì nó truyền hai chuỗi thay vì hai số vào hàm add():

let sum = add('10', '20');

Lỗi:

error TS2345: Argument of type '"10"' is not assignable to parameter of type 'number'

Kiểu dữ liệu của tham số hàm cũng có sẵn trong thân hàm để kiểm tra kiểu.

Khai báo : number sau dấu ngoặc đơn chỉ định kiểu dữ liệu trả về của hàm. Hàm add() trả về một giá trị kiểu number trong trường hợp này.

Khi một hàm có kiểu trả về, trình biên dịch TypeScript sẽ kiểm tra mọi câu lệnh return với kiểu dữ liệu trả về để đảm bảo rằng giá trị trả về tương thích với nó.

Nếu một hàm không trả về giá trị, bạn có thể sử dụng kiểu void làm kiểu trả về. Từ khóa void chỉ ra rằng hàm không trả lại bất kỳ giá trị. Ví dụ:

function echo(message: string): void {
    console.log(message.toUpperCase());
}

Lệnh void ngăn không cho mã bên trong hàm trả về một giá trị và ngăn mã gọi gán kết quả của hàm cho một biến.

Khi bạn không chú thích kiểu dữ liệu trả về, TypeScript sẽ cố gắng suy ra một kiểu dữ liệu thích hợp. Ví dụ:

function add(a: number, b: number) {
    return a + b;
}

Trong ví dụ này, trình biên dịch TypeScript cố gắng suy ra kiểu trả về của hàm add() là kiểu number.

Tuy nhiên, nếu một hàm có các nhánh khác nhau trả về các kiểu dữ liệu khác nhau, trình biên dịch TypeScript có thể suy ra kiểu kết hợp hoặc kiểu any.

Do đó, điều quan trọng là phải thêm chú thích kiểu dữ liệu vào một hàm càng nhiều càng tốt.

Kiểu hàm trong TypeScript

Giới thiệu về kiểu hàm trong TypeScript

Kiểu hàm (function type) trong TypeScript có hai phần: tham số và kiểu trả về. Khi khai báo một kiểu hàm, bạn cần chỉ định cả hai phần bằng cú pháp sau:

(parameter: type, parameter:type,...) => type

Ví dụ sau đây cho thấy cách khai báo một biến có kiểu hàm nhận hai số và trả về một số:

let add: (x: number, y: number) => number;

Trong ví dụ này:

  • Kiểu hàm chấp nhận hai đối số: xy kiểu number.
  • Kiểu dữ liệu của giá trị trả về là number theo sau mũi tên (=>) xuất hiện giữa các tham số và kiểu trả về.
Lưu ý rằng tên tham số (xy) chỉ dành cho mục đích dễ đọc. Miễn là các kiểu tham số phù hợp, nó là một kiểu hợp lệ cho kiểu hàm.

Sau khi chú thích một biến có kiểu hàm, bạn có thể gán function có cùng kiểu cho biến đó.

Trình biên dịch TypeScript sẽ khớp số lượng tham số với kiểu dữ liệu của chúng và kiểu dữ liệu trả về.

Ví dụ sau đây cho thấy cách gán một function cho biến add:

add = function (x: number, y: number) {
    return x + y;
};

Ngoài ra, bạn có thể khai báo một biến kiểu hàm và gán một hàm cho nó như sau:

let add: (a: number, b: number) => number =
    function (x: number, y: number) {
        return x + y;
    };

Nếu bạn gán các hàm khác có kiểu dữ liệu không khớp với biến add, TypeScript sẽ xuất hiện lỗi.

add = function (x: string, y: string): number {
    return x.concat(y).length;
};

Trong ví dụ này, chúng tôi đã gán lại một function có kiểu không khớp với kiểu của biến add.

Kiểu hàm suy luận trong TypeScript

Trình biên dịch TypeScript có thể tìm ra kiểu dữ liệu của function khi bạn có kiểu dữ liệu ở một phía của phép gán. Dạng kiểu suy luận này được gọi là kiểu dữ liệu theo ngữ cảnh. Ví dụ:

Kiểu suy luận cho function trong TypeScript

Trong ví dụ này, hàm add sẽ có kiểu (x: number, y:number) => number.

Bằng cách sử dụng kiểu suy luận, bạn có thể giảm đáng kể lượng mã có chú thích.

Tham số tùy chọn của function trong TypeScript

Trong JavaScript, bạn có thể gọi một hàm mà không cần truyền bất kỳ đối số nào mặc dù function có các tham số. Do đó, JavaScript hỗ trợ các tham số tùy chọn theo mặc định.

Trong TypeScript, trình biên dịch kiểm tra mọi lệnh gọi hàm và đưa ra lỗi trong các trường hợp sau:

  • Số lượng đối số khác với số lượng tham số được chỉ định trong hàm.
  • Kiểu dữ liệu của đối số không tương thích với kiểu dữ liệu của tham số của hàm.

Bởi vì trình biên dịch kiểm tra kỹ lưỡng các đối số truyền vào, bạn cần chú thích các tham số tùy chọn để hướng dẫn trình biên dịch không gây ra lỗi khi bạn bỏ qua các đối số.

Để khai báo một tham số hàm tùy chọn, bạn sử dụng ? sau tên tham số. Ví dụ:

function multiply(a: number, b: number, c?: number): number {
    if (typeof c !== 'undefined') {
        return a * b * c;
    }
    
    return a * b;
}

Tham số tùy chọn của function trong TypeScript làm việc như thế nào:

  • Đầu tiên, sử dụng dấu chấm hỏi ? sau  tham số c.
  • Tiếp theo, kiểm tra xem đối số có được truyền vào hàm hay không bằng cách sử dụng biểu thức typeof c !== 'undefined'.
Lưu ý rằng nếu bạn sử dụng biểu thức if(c) để kiểm tra xem một đối số không được khởi tạo hay không, bạn sẽ thấy rằng chuỗi rỗng hoặc số không sẽ được coi là undefined.

Các tham số tùy chọn phải xuất hiện sau các tham số bắt buộc trong danh sách tham số.

Ví dụ: nếu bạn đặt tham số tùy chọn b trước tham số  bắt buộc c, trình biên dịch TypeScript sẽ phát ra lỗi:

function multiply(a: number, b?: number, c: number): number {
    if (typeof c !== 'undefined') {
        return a * b * c;
    }
    return a * b;
}

Lỗi:

error TS1016: A required parameter cannot follow an optional parameter.

Tham số mặc định của function trong TypeScript

Giới thiệu về tham số mặc định của function trong TypeScript

JavaScript hỗ trợ các tham số mặc định kể từ ES2015 (hoặc ES6) với cú pháp sau:

function name(parameter1=defaultValue1,...) {
   // do something
}

Trong cú pháp này, nếu bạn không truyền đối số hoặc truyền undefined vào hàm khi gọi nó, hàm sẽ nhận các giá trị khởi tạo mặc định cho các tham số bị bỏ qua. Ví dụ:

function applyDiscount(price, discount = 0.05) {
    return price * (1 - discount);
}

console.log(applyDiscount(100)); // 95

Trong ví dụ này, hàm applyDiscount() có tham số chiết khấu làm tham số mặc định.

Khi bạn không truyền đối số discount vào hàm applyDiscount(), hàm sẽ sử dụng giá trị mặc định là 0.05.

Tương tự như JavaScript, bạn có thể sử dụng các tham số mặc định trong TypeScript với cùng một cú pháp:

function name(parameter1:type=defaultvalue1, parameter2:type=defaultvalue2,...) {
   //
}

Ví dụ sau sử dụng các tham số mặc định cho hàm applyDiscount():

function applyDiscount(price: number, discount: number = 0.05): number {
    return price * (1 - discount);
}

console.log(applyDiscount(100)); // 95
Lưu ý rằng bạn không thể khai báo các tham số mặc định trong định nghĩa biến kiểu hàm.

Đoạn mã sau sẽ dẫn đến lỗi:

let promotion: (price: number, discount: number = 0.05) => number;

Lỗi:

error TS2371: A parameter initializer is only allowed in a function or constructor implementation.

Tham số mặc định và tham số tùy chọn trong TypeScript

Giống như các tham số tùy chọn , các tham số mặc định cũng là tùy chọn. Nó có nghĩa là bạn có thể bỏ qua các tham số mặc định khi gọi hàm.

Ngoài ra, cả tham số mặc định và tham số tùy chọn sau đây đều có chung một kiểu hàm. Ví dụ:

function applyDiscount(price: number, discount: number = 0.05): number {
  // ...
}

function applyDiscount(price: number, discount?: number): number {
  // ...
}

đều có cùng kiểu hàm:

(price: number, discount?: number) => number

Các thông số tùy chọn phải đứng sau các thông số bắt buộc. Tuy nhiên, các tham số mặc định không cần phải xuất hiện sau các tham số bắt buộc.

Khi một tham số mặc định xuất hiện trước một tham số bắt buộc, bạn cần phải truyền giá trị undefined một cách rõ ràng để nhận giá trị khởi tạo mặc định.

Hàm sau trả về số ngày trong một tháng và năm cụ thể:

function getDay(year: number = new Date().getFullYear(), month: number): number {
    let day = 0;
    switch (month) {
        case 1:
        case 3:
        case 5:
        case 7:
        case 8:
        case 10:
        case 12:
            day = 31;
            break;
        case 4:
        case 6:
        case 9:
        case 11:
            day = 30;
            break;
        case 2:
            // leap year
            if (((year % 4 == 0) &&
                !(year % 100 == 0))
                || (year % 400 == 0))
                day = 29;
            else
                day = 28;
            break;
        default:
            throw Error('Invalid month');
    }
    return day;
}

Trong ví dụ này, giá trị mặc định của năm là năm hiện tại nếu bạn không truyền đối số hoặc truyền giá trị undefined.

Ví dụ sau sử dụng hàm getDay() để lấy số ngày trong tháng 2 năm 2019:

let day = getDay(2019, 2);
console.log(day); // 28

Để nhận được số ngày trong tháng 2 của năm hiện tại, bạn cần truyền giá trị undefined cho tham số năm như sau:

let day = getDay(undefined, 2);
console.log(day);

Tham số còn lại của function trong TypeScript

Tham số còn lại (rest parameters) cho phép một hàm chấp nhận không hoặc nhiều đối số của kiểu dữ liệu được chỉ định. Trong TypeScript, các tham số còn lại tuân theo các quy tắc sau:

  • Một hàm chỉ có một tham số còn lại.
  • Tham số còn lại xuất hiện cuối cùng trong danh sách tham số.
  • Kiểu dữ liệu của tham số còn lại là kiểu mảng.

Để khai báo tham số còn lại của function, bạn đặt trước tên tham số ba dấu chấm (...) và sử dụng kiểu mảng làm chú thích kiểu:

function fn(...rest: type[]) {
   //...
}

Ví dụ sau cho thấy cách sử dụng tham số còn lại:

function getTotal(...numbers: number[]): number {
    let total = 0;
    numbers.forEach((num) => total += num);
    return total;
}

Trong ví dụ này, hàm getTotal() tính tổng các số được truyền vào nó.

Vì tham số của function là tham số còn lại, bạn có thể truyền một hoặc nhiều số để tính tổng:

console.log(getTotal()); // 0
console.log(getTotal(10, 20)); // 30
console.log(getTotal(10, 20, 30)); // 60

Nạp chồng hàm trong TypeScript

Giới thiệu về nạp chồng hàm trong TypeScript

Trong TypeScript, nạp chồng hàm hay quá tải hàm (function overloading) cho phép bạn thiết lập mối quan hệ giữa các kiểu tham số và kiểu kết quả trả về của một hàm.

Lưu ý rằng nạp chồng hàm TypeScript khác với nạp chồng hàm được hỗ trợ bởi các ngôn ngữ kiểu dữ liệu tĩnh khác như C#Java.

Hãy bắt đầu với một số hàm đơn giản:

function addNumbers(a: number, b: number): number {
    return a + b;
}

function addStrings(a: string, b: string): string {
    return a + b;
}

Trong ví dụ này:

  • Hàm addNumbers() trả về tổng của hai số.
  • Hàm addStrings() trả về chuỗi được nối từ hai chuỗi.

Có thể sử dụng kiểu kết hợp để định nghĩa một loạt các kiểu cho các tham số và kết quả của hàm như sau:

function add(a: number | string, b: number | string): number | string {
    if (typeof a === 'number' && typeof b === 'number')
        return a + b;

    if (typeof a === 'string' && typeof b === 'string')
        return a + b;
}

Tuy nhiên, kiểu liên hợp không thể hiện chính xác mối quan hệ giữa các kiểu tham số và kết quả.

Hàm add() cho trình biên dịch biết rằng nó sẽ chấp nhận tham số kiểu số hoặc chuỗi và trả về kiểu số hoặc chuỗi. Nó không thể mô tả rằng hàm trả về một số khi các tham số là số và trả về một chuỗi nếu các tham số là chuỗi.

Để mô tả tốt hơn các mối quan hệ giữa các kiểu được sử dụng bởi một hàm, TypeScript hỗ trợ nạp chồng hàm. Ví dụ:

function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
   return a + b;
}

Trong ví dụ này, chúng tôi đã thêm hai nạp chồng hàm vào hàm add(). Nạp chồng hàm đầu tiên cho trình biên dịch biết rằng khi các đối số là số, hàm add() sẽ trả về một số. Nạp chồng hàm thứ hai thực hiện tương tự nhưng đối với một chuỗi.

Mỗi nạp chồng hàm xác định một tổ hợp các kiểu được hỗ trợ bởi hàm add(). Nó mô tả ánh xạ giữa các tham số và kết quả mà chúng trả về.

Bây giờ, khi bạn gọi hàm add(), trình soạn thảo mã gợi ý rằng có sẵn một hàm quá tải như thể hiện trong hình sau:

Nạp chồng hàm trong TypeScript

Nạp chồng hàm với tham số tùy chọn trong TypeScript

Khi bạn nạp chồng hàm, số lượng tham số bắt buộc phải giống nhau. Nếu nạp chồng hàm có nhiều tham số hơn tham số khác, bạn phải làm cho các tham số bổ sung là tùy chọn. Ví dụ:

function sum(a: number, b: number): number;
function sum(a: number, b: number, c: number): number;
function sum(a: number, b: number, c?: number): number {
    if (c) {
        return a + b + c;
    }
    return a + b;
}

Hàm sum() chấp nhận hai hoặc ba tham số. Tham số thứ ba là tùy chọn. Nếu bạn không làm cho nó tùy chọn, bạn sẽ gặp lỗi.

Nạp chồng phương thức trong TypeScript

Khi một hàm là thuộc tính của một lớp, nó được gọi là một phương thức. TypeScript cũng hỗ trợ nạp chồng phương thức. Ví dụ:

class Counter {
    private current: number = 0;
    count(): number;
    count(target: number): number[];
    count(target?: number): number | number[] {
        if (target) {
            let values = [];
            for (let start = this.current; start <= target; start++) {
                values.push(start);
            }
            this.current = target;
            return values;
        }
        return ++this.current;
    }
}

Phương thức count() có thể trả về một số hoặc một mảng tùy thuộc vào số lượng các tham số bạn truyền vào cho nó:

let counter = new Counter();

console.log(counter.count()); // return a number
console.log(counter.count(20)); // return an array

Đầu ra:

1
[
   1,  2,  3,  4,  5,  6,  7,
   8,  9, 10, 11, 12, 13, 14,
  15, 16, 17, 18, 19, 20     
]

Tóm lược

  • Sử dụng chú thích kiểu cho các tham số hàm và kiểu trả về để giữ cho mã gọi nội tuyến và đảm bảo kiểm tra kiểu bên trong thân hàm.
  • Sử dụng cú pháp parameter?: type để khai báo một tham số tùy chọn.
  • Sử dụng biểu thức typeof(parameter) !== 'undefined' để kiểm tra xem tham số đã được khởi tạo chưa.
  • Sử dụng cú pháp tham số mặc định parameter:=defaultValue nếu bạn muốn đặt giá trị khởi tạo mặc định cho tham số.
  • Các tham số mặc định là tùy chọn.
  • Để sử dụng giá trị khởi tạo mặc định của một tham số, bạn bỏ qua đối số khi gọi hàm hoặc truyền giá trị undefined vào hàm.
  • Nạp chồng hàm (quá tải hàm - overloading) trong TypeScript cho phép bạn mô tả mối quan hệ giữa các kiểu tham số và kết quả của một hàm.
TypeScript
Bài Viết Liên Quan:
Namespace trong TypeScript
Trung Nguyen 14/10/2021
Namespace trong TypeScript

Namespace được sử dụng để nhóm logic các chức năng. Namespace có thể bao gồm các interface, lớp, hàm và biến để hỗ trợ một nhóm các chức năng liên quan.

Từ khóa readonly và static trong TypeScript
Trung Nguyen 13/10/2021
Từ khóa readonly và static trong TypeScript

Trong hướng dẫn này bạn sẽ tìm hiểu về từ khóa readonly, static và cách sử dụng chúng trong TypeScript.

Từ khóa kiểm soát truy cập trong TypeScript
Trung Nguyen 12/10/2021
Từ khóa kiểm soát truy cập trong TypeScript

TypeScript có ba từ khóa kiểm soát quyền truy cập: public, private và protected để kiểm soát khả năng hiển thị của các thành phần dữ liệu của nó.

Abstract Class trong TypeScript
Trung Nguyen 11/10/2021
Abstract Class trong TypeScript

Định nghĩa một abstract class trong TypeScript bằng cách sử dụng từ khóa abstract. Abstract class chủ yếu được sử dụng để các lớp khác kế thừa từ chúng. Chúng ta không thể tạo một thể hiện của một abstract class.