Generic

Generics trong TypeScript là một tính năng mạnh mẽ cho phép bạn tạo ra các thành phần (hàm, lớp, interface, và kiểu) mà có thể làm việc với nhiều loại dữ liệu khác nhau mà không cần chỉ định trực tiếp kiểu dữ liệu đó. Generics giúp tái sử dụng mã và tạo ra các thành phần linh hoạt và tuỳ chỉnh theo nhu cầu.

Generic Function

function getArray<T>(items: T[]) : T[] { return new Array<T>().concat(items); } let myNumArr = getArray<number>([100,200,300]); let myStrArr = getArray<string>(["Hello", "World"]); myNumArr.push(400); myStrArr.push("Hello Typescript"); myNumArr.push("Hi"); // Compiler Error: Argument of type 'string' is not assignable to parameter of type 'number'. myStrArr.push(500); // Compiler Error: Argument of type 'number' is not assignable to parameter of type 'string'.

Trong ví dụ trên, biến kiểu T được chỉ định với hàm trong dấu ngoặc nhọn getArray<T>. Biến kiểu T cũng được sử dụng để chỉ định kiểu của các đối số và giá trị trả về. Điều này có nghĩa là kiểu dữ liệu được chỉ định tại thời điểm gọi hàm cũng sẽ là kiểu dữ liệu của các đối số và giá trị trả về.

Chúng ta có thể chỉ định nhiều biến kiểu với các tên khác nhau như được thể hiện dưới đây.

function displayType<T, U>(id:T, name:U): void { console.log(typeof(id) + ", " + typeof(name)); } displayType<number, string>(1, "Steve"); // number, string

Khi sử dụng biến kiểu để tạo các thành phần generic, TypeScript buộc chúng ta phải sử dụng chỉ các phương thức chung tổng quát có sẵn cho mọi kiểu dữ liệu.

function displayType<T, U>(id:T, name:U): void { id.toString(); // OK name.toString(); // OK id.toFixed(); // Compiler Error: 'toFixed' does not exists on type 'T' name.toUpperCase(); // Compiler Error: 'toUpperCase' does not exists on type 'U' console.log(typeof(id) + ", " + typeof(name)); }

Trong ví dụ trên, phương thức id.toString() và name.toString() là chính xác vì phương thức toString() có sẵn cho tất cả các kiểu dữ liệu. Tuy nhiên, các phương thức cụ thể cho từng kiểu dữ liệu như toFixed() cho kiểu number hoặc toUpperCase() cho kiểu string không thể được gọi. Trình biên dịch sẽ báo lỗi trong trường hợp này.

Generic Interface

Trong TypeScript, một generic interface (giao diện tổng quát) cho phép bạn định nghĩa một giao diện có thể được sử dụng với nhiều kiểu dữ liệu khác nhau. Điều này giúp tạo ra các thành phần linh hoạt và tái sử dụng được trong mã TypeScript.

Dưới đây là một ví dụ về việc sử dụng generic interface trong TypeScript:

interface KeyPair<T, U> { key: T, value: U } let kv1: KeyPair<number,string> = {key: 1, value: "Steve"}; let kv2: KeyPair<number,number> = {key: 1, value: 123};
interface KeyValueProcessor<T, U>{ (key: T, value: U): void; } function processNumberKeyPairs(key:number, value:number): void{ console.log('processNumKeyPairs: key = ' + key + ', value = ' + value) } function processStringKeyPairs(key: number, value: string): void{ console.log('processStringKeyPairs: key = '+ key + ', value = ' + value) } let numKVProcessor: KeyValueProcessor<number, number> = processNumberKeyPairs; numKVProcessor(1, 123456); // "processNumKeyPairs: key = 1, value = 123456" let strKVProcessor: KeyValueProcessor<number, string> = processStringKeyPairs; strKVProcessor(1, "Steve"); // "processStringKeyPairs: key = 1, value = Steve"

Trong ví dụ trên, generic interface KeyValueProcessor bao gồm chữ ký tổng quát của một phương thức mà không có tên phương thức. Điều này cho phép chúng ta sử dụng bất kỳ hàm nào có chữ ký phù hợp. Kiểu tổng quát sẽ được thiết lập vào thời điểm tạo biến như numKVProcessor và strKVProcessor. Có thể viết lại ví dụ trên như sau:

interface KeyValueProcessor<T, U>{ (key: T, value: U): void; } function processKeyPairs<T, U>(key: T, value: U):void{ console.log(`processKeyPairs: key = ${key}, value = ${value}`) } let numKVProcessor: KeyValueProcessor<number, number> = processKeyPairs; numKVProcessor(1, 12345); //Output: processKeyPairs: key = 1, value = 12345 let strKVProcessor: KeyValueProcessor<number, string> = processKeyPairs; strKVProcessor(1, "Bill"); //Output: processKeyPairs: key = 1, value = Bill

Generic Class

Trong TypeScript, bạn cũng có thể sử dụng generic class (lớp tổng quát) để tạo ra các lớp có thể hoạt động với nhiều kiểu dữ liệu khác nhau. Generic class cho phép bạn tham số hóa kiểu dữ liệu của lớp trong quá trình khai báo và sử dụng lại mã nguồn mà không cần phải sao chép và chỉnh sửa mã cho từng kiểu dữ liệu riêng biệt.

Dưới đây là một ví dụ về việc sử dụng generic class trong TypeScript:

class Pair<T> { first: T; second: T; constructor(first: T, second: T){ this.first = first; this.second = second } swap():void{ const temp: T = this.first; this.first = this.second; this.second = temp; } } const pairOfNumber: Pair<number> = new Pair(10,20); console.log(pairOfNumber.first); // 10 console.log(pairOfNumber.second); // 20 pairOfNumber.swap(); console.log(pairOfNumber.first); // 20 console.log(pairOfNumber.second); // 10
interface IkeyValueProcessor<T, U> { process(key: T, value: U): void; } class kvProcessor<T, U> implements IkeyValueProcessor<T, U>{ process(key: T, value: U):void{ console.log(`Key = ${key}, value = ${value}`); } } let proc: IkeyValueProcessor<number, string> = new kvProcessor() proc.process(1, "Bill"); // "Key = 1, value = Bill"

Tài liệu tham khảo: