Bài 16: TypeScript – Generic
Như các bài viết trước mình đã đề cập, chúng ta đều muốn tối ưu code của mình, giúp code của mình linh động, dễ sử dụng và mở rộng, generic được sinh ra để hỗ trợ các việc kể trên. Bài viết này chúng ta sẽ tìm hiểu về generic trong TypeScript, các khái niệm cơ bản và cách dùng nhé.
Nội dung của bài
Generic trong TypeScript
Như chúng ta đã biết, trong TypeScript mọi thứ đều phải định nghĩa kiểu rõ ràng, generic giúp cho việc xây dựng các hàm, các modules .. một cách linh động hơn, các thành phần không bị bó buộc vào một kiểu (type) nhất định. Hiểu nôm na cơ chế của generic là chúng ta có thể xây dựng các thành phần trước và định nghĩa kiểu cho chúng khi sử dụng chúng.
Chúng ta đi vào một ví dụ cụ thể nhé:
Bây giờ chúng ta cần tạo một hàm, tạo ra một Array nhận các tham số đầu vào là một kiểu bất kỳ. Trong TypeScript, các tham số đầu vào đều phải được định nghĩa kiểu cụ thể, vậy ý tưởng đầu ta về việc tạo hàm cùng type any
function getArray(items : any[] ) : any[] {
return new Array().concat(items);
}
let stringArr = getArray(["Tom", "Bob", "Justice"])
let numbArr = getArray([1,2,3,4,5,6,7])
Tuy nhiên, chúng ta có thể thêm bất kỳ phần tử, type nào vào mảng mà chúng ta tạo sẵn, như vậy là type của cả mảng sẽ không nhất quán
stringArr.push("Emmy") // "Tom", "Bob", "Justice", "Emmy"
stringArr.push(4) // "Tom", "Bob", "Justice", 4
Như các bài học trước, chúng ta đã biết về Union. Sử dụng Union cũng là một cách để giải quyết bài toán này
function getUnionArray(items: String[] | Number[] ) : String[] | Number[] {
return new Array().concat(items);
}
let unionList = getUnionArray(["Tom", "Bob", "Justice"])
unionList.push(4) // Err: Argument of type 'number' is not assignable to parameter of type 'String & Number'.
Type 'number' is not assignable to type 'String'.
Với Union chúng ta có thể giải quyết được vấn đề trên. Tuy nhiên có một vấn đề đặt ra đó là chúng ta phải định nghĩa hết tất cả các Type mà có thể xuất hiện, như vậy thì có thể code sẽ rất dài, rất khó đọc hoặc nếu các tham số đầu vào là các interface type thì rất khó để handle hết
function getUnionArray(items: String[] | Number[] | Boolean[] | IEmployee[]) : String[] | Number[] | Boolean[] | IEmployee[] {
...
}
Generic sinh ra và giải quyết vấn đề trên. TypeScript sử dụng <keyword> (keyword có thể là bất cứ từ gì, nhưng không được trùng với tên của các kiểu nguyên thuỷ) đánh dấu việc sử dụng generics, nó ghi nhớ lại type mà chúng ta cung cấp và chỉ làm việc với type đó.
Sử dụng Generic
Generic types
function getArray<T>(items: T[]) : T[] {
return new Array().concat(items);
}
let stringArr = getArray<string>(["Tom", "Bob", "Justice"])
let numbArr = getArray<number>([1,2,3,4,5,6,7])
Ở ví dụ trên, hàm getArray được khai báo, sử dụng generic được đánh dấu bởi <T>. khi khởi tạo stringArr với hàm getArray, chúng ta thay thế <T> với <string>, lúc đó hàm getArray<T> của chúng ta sẽ trở thành
function getArray(items: string[]) : string[] {
return new Array().concat(items);
}
Và nó sẽ chỉ nhận các mảng string làm tham số đầu vào và đầu ra
stringArr.push("Emmy") // ["Tom", "Bob", "Justice", "Emmy"]
stringArr.push(4) // Argument of type 'number' is not assignable to parameter of type 'string'.ts
Generic interface
Chúng ta có thể sử dụng generic để tạo các interface một cách linh hoạt hơn. Bên cạnh đó, có thể sử dụng nhiều generic đại diện cho từng type một trong inteface của mình
interface IElement<T, U> {
key: T;
value: U;
}
let newElement: IElement<number, string> = { key: 0, value: "Academy"}
let compElement: IElement<string, string> = { key: "Company", value: "Yeulaptrinhvn"}
Ở trên, chúng ta thấy type T và U đã được khai báo và chúng được thay thế linh hoạt bởi các type khác tuỳ theo người dùng trong quá trình sử dụng.
Generic Class
Tương tự như generice interface, generic còn hỗ trợ việc tạo class linh hoạt hơn
class ObjectElement<T,U>
{
private key: T;
private val: U;
constructor(key: T, val: U) {
this.key = key;
this.val = val;
}
display():void {
console.log(`Key = ${this.key}, val = ${this.val}`);
}
}
let newObjectElement = new ObjectElement<number, string>(0, "Academy") // { key: 0, val: 'Academy' }
Chúng ta còn có thể kế thừa class từ generic interface
interface IElement<T,U> {
key: T;
val: U;
display (): void
}
class ObjectElement<T, U> implements IElement<T, U>
{
key: T;
val: U;
constructor(key: T, val: U) {
this.key = key;
this.val = val;
}
display():void {
console.log(`Key = ${this.key}, val = ${this.val}`);
}
}
let newObjectElement = new ObjectElement<number, string>(0, "Academy")
Summary
Vậy là chúng ta đã tìm hiểu những khái niệm cơ bản, cách sử dụng cơ bản về generic trong TypeScript. Hi vọng bài viết sẽ bổ ích , cung cấp nhiều thông tin cho các bạn trong quá trình tìm hiểu về TypeScript.
Rất mong nhận được sự đóng góp của các bạn để chúng mình hoàn thiện bài viết hơn nữa.