+2

Bí kíp chú thích hàm trong Typescript: từ cơ bản đến nâng cao

TypeScript là ngôn ngữ lập trình ngày càng phổ biến, đặc biệt trong phát triển web. Bài viết này sẽ hướng dẫn bạn cách chú thích hàm trong TypeScript, từ những kiến thức cơ bản đến các kỹ thuật nâng cao như generic và interface.

Cách cơ bản nhất để chú thích một hàm trong TypeScript là như sau:

function add(a: number, b: number): number{
 // logic
}

// or

const add = (a: number, b: number): number => {
    //logic
}

Các tham số tùy chọn được chú thích bằng dấu hỏi chấm param? như sau:

function optional(a: number, b?: number){
    console.log(a)
}

Nhưng còn các hàm callback, hoặc các hàm mà kiểu của đối số và kiểu trả về có liên quan (trong trường hợp đó, là generic) thì sao?

Giá trị hàm

Hãy quan sát hàm JavaScript này:

function multiplier(factor){
    return value => factor * value
}

Vì vậy, chúng ta có thể sử dụng hàm như sau:

const n  = multiplier(6)
n(8) // 48

Giá trị hàm được chú thích trong TypeScript như sau:

(param1: type, param2: type)=> type

// or

const add: (a: number, b: number)=> number = function (a, b){
    return a + b
}

Vì vậy, để chú thích hàm multiplier, chúng ta có:

function multiplier(factor: number): (value: number) => number{
    return value => factor * value
}

IDE của bạn (tốt nhất là VSCode) sẽ tự động suy ra kiểu của value (là number) trong logic hàm.

Hàm Generic

Chú thích hàm này là sai:

function pickFirst(array: Array<T>): T{
    return array[0]
}

Đương nhiên, điều này gây ra lỗi:

"Cannot find name T"

Do đó, định dạng chú thích các hàm generic là:

function fnName <T,U>(param: T): U{
    //logic
}

Hãy quan sát vị trí khai báo tên generic. Vì vậy, để chú thích chính xác hàm trên:

function pickFirst<T>(array: Array<T>): T{
    return array[0]
}

Bây giờ nó hoạt động.

Nhưng còn các hàm Generic có tham số hàm thì sao?

Ví dụ: chúng ta muốn chú thích một hàm map tùy chỉnh có tên myMap được viết như sau trong Javascript:

function myMap(arr, fn){
    rreturn arr.map(fn)
}

Chúng ta có thể chú thích nó như sau:

function myMap<Input, Output>(arr: Input[], fn: (item: Input, index?: number) => Output): Output[]{
    return arr.map(fn)
}

Điều chúng ta cần lưu ý trong đoạn mã trên là hàm fn nhận một mục cùng kiểu với mảng Input và trả về một kiểu Output.

Còn phương thức tĩnh Array.from() thì sao?

function myFrom<Type>(iterable: Iterable<Type>): Array<Type>{
    // logic
}

Hoặc phương thức array.filter()?

function myFilter<Input>(arr: Input[], fn: (item: Input) => boolean): Input[]{
    return arr.filter(fn)
}

Ràng buộc Generic

Bạn cũng có thể ràng buộc các biến generic với các kiểu có một thuộc tính cụ thể. Ví dụ:

function pickInnerFirst<T extends {length: number}>(array: Array<T>): T{
    return array[0][0]
}

Hàm này chọn phần tử đầu tiên trong một mảng hai chiều.

Điều này đảm bảo rằng nó chỉ hoạt động cho mảng và chuỗi (chủ yếu) theo cách mà:

pickOne([12, 20, 30]) // wont work.

Chúng ta cũng có thể sử dụng interface:

interface Lengthly{
    length: number;
}

function pickInnerFirst<T extends Lengthly>(array: Array<T>): T{
    return array[0][0]
}

Interface Hàm

Trong trường hợp bạn chưa biết, hàm là các đối tượng và chúng có các thuộc tính:

const add = (a, b) => a + b

console.log(add.toString()) //(a, b) => a + b

// also
console.log(Object.getPrototypeOf(add) == Function.prototype) //true
console.log(Object.getPrototypeOf(Function.prototype) == Object.prototype) //true

Ví dụ thứ hai chỉ ra rằng hàm add là con của Object.prototype (mặc dù nó không phải là con trực tiếp) và do đó là một đối tượng.

Điều thú vị là, bạn có thể gắn các thuộc tính vào một hàm trong JavaScript:

function shout(fn, value){
    fn(value)
    console.log(fn.description)
} 

function hey(value){
    console.log(value)
}

hey.description = `A function called ${hey.name}` // A

shout(hey, "Hola!!!")

Đoạn mã này sẽ trả về:

>> Hola!!!
>> A function called hey

Hãy quan sát rằng tại A, chúng ta đã gắn một thuộc tính có tên description vào hàm, điều này chỉ có thể thực hiện được với các đối tượng.

Điều đó để kết luận rằng: các đối tượng có thể được mô tả bằng interface (thông thường). Đối với các hàm, điều này cũng có thể thực hiện được. Tuy nhiên, đó chỉ là điều bạn nên cân nhắc khi bạn đang thêm các thuộc tính đặc biệt vào các hàm của mình.

interface describableFunction{
    (value: string): void; //A
    description: string;
}

Tại chú thích A là nơi chúng ta mô tả các đối số và kiểu trả về của hàm.

Vì vậy, để chú thích mã trước đó trong TypeScript:

interface describableFunction{
    (value: string): void;
    description: string;
}

function shout(fn: describableFunction, value: string) { //A
    fn(value)
    console.log(fn.description)
}

function hey(value: string) {
    console.log(value)
}

hey.description = `A function called ${hey.name}`
shout(hey, "Hola!!!")

Hãy quan sát cách chúng ta sử dụng interface trong hàm shout tại A.

Tại sao bạn nên hiểu cách chú thích hàm trong TypeScript?

Việc chú thích hàm trong TypeScript giúp xây dựng một hệ thống kiểu thông minh hơn, từ đó giảm khả năng xảy ra lỗi. Nếu bạn đang xây dựng một thư viện hoặc framework, bạn có thể phải sử dụng một số hoặc hầu hết các khái niệm được đề cập ở đây. Nếu bạn chỉ đang sử dụng thư viện, những khái niệm này không thực sự cần thiết, mặc dù chúng có thể giúp bạn hiểu sâu hơn về các thư viện bạn đang làm việc.

Cảm ơn các bạn đã theo dõi!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí