Generic trong TypeScript


Generic trong TypeScript


Generic trong Typescript

Generic nói đủ là generic data type – là cách thức giúp tổng quát hơn để tạo và sử dụng các thành phần trong ứng dụng.

Cụ thể hơn, sử dụng generic type là để truyền type vào cho function, class, interface như là tham số. Nhờ vậy mà code chạy linh động hơn.  Vì nhiều kiểu dữ liệu khác nhau có thể được dùng chung cho 1 xử lý nào đó.

Hảm xulyso sau đây chỉ chạy khi x có kiểu number, trả về cũng number.

function xulyso(x:number):number{
    return x;
}

Đối với các ngữ cảnh cần sự linh động kiểu dữ liệu thì viết vậy hơi gò bó. Lúc này có thể xử lý bằng generic

Định nghĩa hàm generic

Còn cách nữa rất linh động trong việc sử dụng hàm với các loại dữ liệu khác nhau. Đó là sử dụng 1 dấu hiệu đại diện cho kiểu dữ liệu  sẽ truyền vào hoặc trả về từ hàm.

function xuly<T>( p1: T): T { return p1; }

Ở đây T (hay chữ gì cũng được) là đại diện cho 1 kiểu dữ liệu (ví dụ number, string…) sẽ khai báo khi gọi hàm.  Hàm xuly viết thế này gọi là generic, nó làm việc với nhiều type. Không giống như khi dùng any. Khi gọi bạn biết được kiểu dữ liệu của tham số và giá trị trả về.

Gọi hàm generic

Có 2 cách gọi hàm generic. Thứ nhất là truyền tường minh <type> sau tên hàm  :

function xuly<T>( p1: T): T { return p1; }
let hoten = xuly<string>("Trần Nhân Tông");
let nam = xuly<number>(1258);

Cách hai là để cho TypeScript tự suy luận kiểu theo giá trị tham số truyền tới

let cha:string = xuly("Trần Thánh Tông");

Sử dụng các biến generic trong typescript

Ví dụ 1 :

const listsp = [ 
    {ten:'Cà chua',gia:30000, cònhàng:true}, 
    {ten:'Chuổi',gia:14000, cònhàng:false}, 
    {ten:'Cam thảo',gia:82000, cònhàng:true},
    {ten:'Chà Là',gia:194000, cònhàng:true}
]
type ThongTinSP = number|string|boolean;
function xuly<T extends ThongTinSP>( p: T):T[] {
  let arr: T[] =[];
  switch( typeof p){
    case 'number': 
      arr =  listsp.map( e => e.gia) as T[]; break;
    case 'string': 
      arr =  listsp.map( e => e.ten) as T[]; break;
    case 'boolean': 
      arr =  listsp.map( e => e.cònhàng) as T[]; break;
    default : arr=[];
  }
  return arr;
}
let arr1:number[] = xuly<number>(1); console.log(arr1); 
let arr2:string[] = xuly<string>(''); console.log(arr2); 
let arr3:boolean[] = xuly<boolean>(true); console.log(arr3); 
let arr4:boolean[] = xuly<undefined>(undefined); console.log(arr4);

Ví dụ 2 :

type ketqua = number | string | boolean;
type sinhvienthi = [string,ketqua ];
let arr: sinhvienthi[] = [];
function themsv<T extends ketqua>( msv: string, kq: T  ):T  {
    arr.push([msv, kq]);
    return kq;
}
themsv<string>('pd12345', "Đậu"); 
themsv<boolean>('pd54321', true);
themsv<number>('ps12345', 8); 
console.log(arr);

Generic function

Là function có chỉ định tham số <type> trong định nghĩa của hàm. Type được liệt kê  ngay sau tên hàm. Type gì thì sẽ được chỉ định khi hàm được gọi chứ không cần nói rõ lúc khai báo. Nhờ đó mà hàm có thể làm việc trên nhiều type khác nhau.

function xuly<T>(arg: T): T { return arg;}
let xu_ly = <T>(arg: T): T =>  arg; 
console.log( xuly<number>(5) ) ;
console.log( xuly<string>("Thứ sáu") ) ;
console.log( xuly<boolean>(true) ) ; 
console.log( xu_ly<number>(5) ) ;

Ví dụ 2:

function thuchien<T1, T2>( param1:T1, param2: T2):void { 
    console.log(param1 + ' - '+ param2);
}
thuchien<string, number>("Gạo", 25000);
thuchien<number, string>(25000, "Gạo");
thuchien<string, string>("Gạo", "25000 đồng");

Ví dụ 3: Sử dụng type mảng

function ghepGiaTri<Kieu>( arr: Kieu[]):void { 
    console.log(arr.join(' - '));
}
ghepGiaTri<string>(["Đông Bộ Đầu", "Hàm Tử", "Bạch Đằng"]);
ghepGiaTri<number>([1258, 1285, 1288]);

Generic interface

Generic type cũng có thể dùng trong interface. Sau đây là generic interface

interface ITest<T> { 
    variable1 : T;
    method1( a: T , b: T) : T;
}

Trong code trên, ITest là 1 generic interface với T là đại diện cho 1 type nào đó. ITest interface chứa các field  generic variable1 và generic method là method1() , method này có 2 tham số generic type parameters và trả về 1 generic type. Các khai báo trong interface là mẫu, là cấu trúc cho các biến dựa theo interface như code sau:

let x:ITest<number> = { 
    variable1:0,
    method1(a, b) { return a+b; }
}
let y:ITest<string> = { 
    variable1:"",
    method1(a, b) { return (a+b).toUpperCase(); }
}

Sau đây là ví dụ khác :

interface IXuly<Type> { ketqua:Type; xuly(param:Type): Type}
let tangDiem:IXuly<number> = { 
    ketqua:1, 
    xuly: function(n) { 
        this.ketqua = n+1; 
        return this.ketqua;
    }
}
console.log( tangDiem.xuly(9) , tangDiem );

let chuhoa:IXuly<string> = { 
    ketqua:"", 
    xuly: function(n:string) { 
        this.ketqua = n.toUpperCase(); 
        return this.ketqua;
    }
}
console.log( chuhoa.xuly("viet nam") , chuhoa );

Interface trong TypeScript có thể được sử dụng như 1 type. Và kéo theo generic interface cũng dùng được như type. Mời xem ví dụ sau:

interface ICaNhan<T, U> { info1: T; info2: U; }
let k1:ICaNhan<string, number> = { info1:"Tèo", info2: 20 };
let k2:ICaNhan<string, boolean> = { info1:"Út vẹo", info2:true }; 
console.log(k1);
console.log(k2);

Và cũng có thể dùng generic interface như là function type (kiểu dữ liệu function).

interface IThietBi<T, U>{ ( ten: T, gia: U): void; };
function hamA(ten:string, gia:number):void { 
  console.log(`Tên = ${ten} , giá = ${gia}`);
}
function hamB(ten: string, gia:string):void { 
  console.log(`Tên = ${ten} , giá = ${gia}`);
}
let tb1: IThietBi<string, number> = hamA;
tb1('Ổ cắm thông minh', 152000); 
let tb2: IThietBi<string, string> = hamB;
tb2('Quạt hút mùi', '3500000 đồng');

Generic class

Một generic class là class có tham số type (chưa biết cụ thể type gì) đặt trong dấu < > ở ngay phía sau tên của class lúc khai báo.

Các thuộc tính trong generic class cũng cò thể dùng type trừu tượng này. Khi nào tạo đối tượng thì type gì sẽ truyền cụ thể.  Điều này giúp cho các thuộc tính trong class dùng chung 1 type thống nhất

class G_Test<T> { zeroValue: T; cong: (x: T, y: T) => T; }
let a = new G_Test<number>();
a.zeroValue = 0;
a.cong = (x, y) => x + y;
console.log(a.cong(8, 3));

let b = new G_Test<string>(); 
b.zeroValue = "";
b.cong =  (x, y) =>  x + y
console.log(b.cong("8", "3"));
console.log(b.cong("Tử", " tế"));

let c = new G_Test<boolean>(); 
c.zeroValue = false;
c.cong =  (x, y) =>  x || y;

Ràng buộc type trong generic – Generic constraints

Nếu bạn muốn type trong generic chỉ làm việc với 1 vài kiểu dữ liệu nào đó thì sao?  Sử dụng từ khóa extends để khai báo ràng buộc cho type

class G_Test<T extends number|string> {
    zeroValue: T; cong: (x: T, y: T) => T;
}
let a = new G_Test<number>(); //ok
let b = new G_Test<string>(); // ok
let c = new G_Test<boolean>(); //lỗi 

Dùng type trong generic contraint

Có thể khai báo sử dụng type trong phạm vi ràng buộc của 1 type khác. Ví dụ lấy tên thuộc tính của đối tượng.

function lấy1<Type, Key extends keyof Type>(obj: Type, key: Key) {
    if (key in <Object>obj) return obj[key];
    else return "Key không có nha";
};
let x = { a: "Tin tưởng", b: "Nhiệt huyết", c: "Yêu thương", d: "Thanh thản" };
console.log ( lấy1(x, "b") ) ; //Nhiệt huyết

let y = [ 15, "Thời đại Lý Trần", 185000 ];
console.log ( lấy1(y, 1) );

Cần đọc thêm thì xem các trang này:

Generic trong TypeScript là cách viết code hay , giúp bạn linh động hơn khi dùng hàm, class, interface của mình . Nên code sẽ ngắn gọn, thời gian thực hiện dự án nhanh hơn.