Decorator trong TypeScript hướng dẫn cách tạo và sử dụng các loại decorator như class,factory. property và method decorator…
Decorator trong TypeScript
- Giới thiệu decorator trong typescript
- Cài đặt decorator
- Khai báo decorator
- Các loại decorator
- Class decorator
- Decorator factory
- Property decorator
- Method decorator
- Accessor decorator
- Parameter decorator
Giới thiệu decorator trong typescript
Decorator – trang trí, đính kèm, bổ sung – là cách thức gắn kèm 1 hàm với class, method, property, accessor. Mục đích của việc gắn vào là để chạy hàm trong runtime mỗi khi các khai báo được sử dụng. Hàm decorator có nhiệm vụ thay đổi, bổ sung cho đối tượng được decorate.
Cài đặt decorator
Để dùng được các decorator, bạn khai báo giá trị true cho thuộc tính experimentalDecorators trong file tsconfig.json
{
"compilerOptions": {
...,
"experimentalDecorators": true
},
}
Khai báo decorator
Khai báo decorator đơn giản, bằng cách sử dụng cú pháp @tênhàm . Trong đó tenhàm là tên một hàm sẽ được gọi khi runtime. Ví dụ
@f @g method() { } hoặc @f @g method() { }
Các loại decorator
Có nhiều loại decorator như class, factory, property, accessor, parameter…
Class decorator
Là loại decorator được chỉ định ngay trước khai báo class. Class decorator gắn vào constructor của class để thay thế, mở rộng class, bổ sung thuộc tính cho class.
Class decorator được gọi lúc class được khai báo chứ không phải lúc 1 instance của class được tạo.
Sử dụng class decorator
Ví dụ sau cho thấy class decorator được chạy 1 lần lúc gặp khai báo class, không chạy khi tạo các đối tượng.
function ThuCungEx(constructor: Function) { console.log("Đây là hàm ThuCung Ex"); } @ThuCungEx class ThuCung { constructor(private ten:string, private tuoi:number){} } let tc1 = new ThuCung('Nô nô', 9); let tc2 = new ThuCung('Mập', 2); console.log("tc1=", tc1) console.log("tc2=", tc2)
Thêm thuộc tính vào class dùng class decorator
Để thêm thuộc tính phai, và ngaytao vào class HocVien, code trong decorator BaseHV và gắn vào class HocVien như sau:
function BaseHV( constructor: Function) { constructor.prototype.phai = true; constructor.prototype.ngaytao = new Date().toLocaleString('vi'); } @BaseHV class HocVien { constructor(public ht: string) {} } let hv1= new HocVien('Tèo'); console.log(hv1);
Thêm thuộc tính dùng class decorator
Dùng class decorator để return class mới mở rộng class hiện tại với các thuộc tính bổ sung
function themTT<T extends { new (...args: any[]): {} }>(constructor: T) { return class extends constructor { mauxe:string = 'Xanh'; }; } @themTT class XeMay { constructor( private tx:string, private gia:number){ } } const x1 = new XeMay('Vision 125', 39.5); console.log(x1, x1["mauxe"]);
Ví dụ dùng class decorator đổi nội dung class
function ChangeHS(constructor: Function):any { return class { private hoten:string; public phai:boolean; constructor(h:string){ this.hoten = h; this.phai = true; } } } @ChangeHS class HocSinh { public name: string; constructor( h:string ) { this.name=h;} } let u1= new HocSinh("Tèo"); console.log(u1);
Sử dụng kết hợp nhiều class decorator
function BaseUser1(constructor: Function) { console.log(`Đây là hàm BaseUser 1`); } function BaseUser2(constructor: Function) { console.log(`Đây là hàm BaseUser 2`); } @BaseUser1 @BaseUser2 class User { constructor(public name: string) {} } let u1 = new User('Lượm'); console.log(u1);
Decorator factory
Đây là một hàm trả về chính hàm decorator. Cụ thể, decorator factory bao bọc quanh 1 hàm decorator để truyền tham số cho nó. Nhờ đó mà việc sử dụng các decorator sẽ uyển chuyển hơn.
Ví dụ 1 dùng decorator factory
function addUserStatus( st:number){ return function(constructor:Function){ constructor.prototype.status= st; } } @addUserStatus(4) class User { constructor(public name: string) {} } let u1 = new User('Lượm'); console.log(u1);
Ví dụ 2 dùng decorator factory
function ChangeHS( st
:number){
return function (constructor: Function):any {
return class {
private hoten:string; tuoi:number;
constructor(h:string){
this.hoten = h;
this.tuoi = st;
}
}
}
}
@ChangeHS(20)
class HocSinh {
public name: string;
constructor( h:string ) { this.name=h;}
}
let u1= new HocSinh("Tèo"); console.log(u1);
Property decorator
Đây là 1 hàm gắn với 1 property trong class nên gọi là property decorator. Hàm decorator này hoạt động với hai tham số là constructor của class và tên thuộc tính. Để sử dụng property decorator, mở file tsconfig.json và khai báo thêm lệnh: “useDefineForClassFields”: false
Property decorator giúp thay đổi, thêm property mới, gán lại data, theo dõi sử dụng properry…
Ôn tập : dùng set get để theo dõi truy cập thuộc tính của class
- Gán cấp độ truy xuất cho thuộc tính là private
- Định nghĩa hàm set có 1 tham số để nhận giá trị mới khi gán và theo dõi giá trị mới có hợp lệ hay không.
- Định nghĩa hàm get (nên cùng tên với hàm set) để trả về giá trị và theo dõi truy cập thuộc tính.
class User { private username:string; private password: string; constructor(u:string, p:string){ this.username = u; this.password = p; } get pass() { console.log(`Lấy pass lúc ${new Date().toLocaleString('vi')} `); return this.password; } set pass(p: string) { console.log(`Gán pass ${p} lúc ${new Date().toLocaleString('vi')}`) this.password =p; } } let u1= new User('teo','huadianh'); console.log(u1); u1.pass='anhsehua'; let p = u1.pass;
Ôn tập : Cách kiểm soát giá trị gán vào cho thuộc tính , báo lỗi nếu giá trị không hợp lệ
class User { private username:string; private password: string; constructor(u:string, p:string){ this.username = u; this.password = p; } get pass() { return this.password; } set pass(p: string) { this.password = p; if (p.length<8){ alert(`Pass ${p} quá ngắn. Không chịu nhoa`); this.password=undefined; } } } let u1= new User('teo','huadianh'); u1.pass='okhua'; //gán pass quá ngắn, báo lỗi như bên dưới
Kiểm soát truy cập thuộc tính bằng cách dùng property decorator
- Chỉ định tên hàm decorator ngay trước thuộc tính cần kiểm soát. Viết theo cú pháp @tênhàm Ví dụ: @TheoDoiMin(7) Truyền các tham số cần thiết cho hàm, nếu bạn cần.
- Định nghĩa hàm decorator. Hàm này được viết như decoration factory.
Thực hiện:
– Tạo class và chỉ định hàm decorator trước thuộc tính password
class User { public username:string; @TheoDoiMin(7) public password: string; constructor(u:string, p:string){ this.username = u; this.password = p; } } let u1 = new User('teo','huadi'); u1.password = 'anhhua'; //Báo lỗi vì quá ngắn <=7 ký tự u1.password = 'anhxinhua'; //Không báo lỗi vì gán hợp lệ let un = u1.username; let pw = u1.password; //Thông báo lấy pass
– Định nghĩa hàm property decorator.
function TheoDoiMin(sokytu: number) { return function( constructor: Object, tenthuoctinh: string) { let value : string; const laygiatri = function() { let now = new Date().toLocaleString('vi'); console.log(`Lấy ${tenthuoctinh} lúc ${now}`); return value; }; const gangiatri = function( newVal: string) { value = newVal; if(newVal.length <= sokytu) console.log(`${tenthuoctinh} ${newVal} ngắn quá,>${sokytu} ký tự`); }; // Khai báo 2 method setter getter để kiểm soát Object.defineProperty(constructor, tenthuoctinh, { get: laygiatri, set: gangiatri }); } }
Ưu điểm của property decorator
- Nhờ decorator mà các thuộc tính của class được kiểm soát chặt chẽ. Trọng tâm là khai báo hàm decorator với lệnh Object.defineProperty để khai báo lại 2 hàm set get nhằm theo dõi các giá trị
- Một hàm property decorator có thể gắn cho nhiều thuộc tính, ví dụ có thể gắn cho username và pass
class User { @TheoDoiMin(10) public username:string; @TheoDoiMin(7) public password: string; constructor(u:string, p:string){ this.username = u; this.password = p; } }
Method decorator
Loại decorator này dùng để gắn với method trong class. Gắn hàm decorator ngay trước tên method. Mục đích gắn hàm này vào method là để can thiệp sửa đổi, thay thế method cần theo dõi. Method decorator sẽ được gọi chạy lúc runtime với 3 tham số:
- Target: là hàm constructor của class
- propertyKey: là tên của method
- PropertyDescriptor các thông tin của method, cấu túc như sau:
{ writable: true, enumerable: true, configurable: true, value: ƒ } Trong đó:
- value là giá trị của method (bản thân hàm)
- writable nếu true thì value có thể thay đổi còn false thì value chỉ đọc read-only.
- enumerable là true thì method được liệt kê khi lặp còn false thì method không được liệt kê.
- configurable là true tức là có thể bị xóa, writable và enumerable có thể thay đổi còn false là không thể
function methodDecorator( chuc: string) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { //viết code ở đây }; }
Ví dụ sử dụng method decorator
class LoiChao { chao: string; constructor( str: string) { this.chao = str; } @đổichào("Chúc an lành") hienloichao() { return "Xin chào! " + this.chao; } } function đổichào( chuc: string) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { descriptor.value = function(){ return chuc.toUpperCase() }; }; } let a = new LoiChao("Khỏe không"); console.log(a); console.log(a.hienloichao());
Accessor decorator
Loại accessor decorator thì giống method decorator nhưng chỉ dùng đế áp dụng cho setter , getter. Typescript không cho dùng 1 decorator trên 1 member (ví dụ pass) cho 2 hành động getter setter. Hàm access decorator sẽ được tự động gọi chạy lúc runtime. Hàm dùng 3 tham số sau
- Hàm constructor của class.
- Tên của member.
- Property Descriptor – thông tin mô tả của member.
let role:number = 0; class User { private _un: string; private _pass: string; constructor(u:string,p:string) { this._un = u; this._pass = p;} @layPass(role) get pass() { return this._pass; } } function layPass( role: number) { return function (target:Object, propertyKey:string, descriptor:PropertyDescriptor) { if (role==0) descriptor.get = () => 'Không đưa nha'; }; } let kh1 = new User("Tèo",'123'); console.log(" Pass =", kh1.pass);
Parameter decorator
Loại parameter decorator dùng cho khai báo cho tham số của method, constructor. Hàm parameter decorator cũng dùng 3 tham số
- target: là hàm constructor của class
- Tên của parameter được decorator
- Index của param trong list các tham số của function
Parameter decorator chỉ được sử dụng để kiểm tra sự tồn tại của params trong function , và thường được dùng kết hợp với method decorator hoặc accessor decorator.
Một parameter decorator chỉ có thể dùng để theo dõi tham số xem nó đã được khai báo trong method hay chưa. Giá trị trả về của parameter decorator sẽ bị bỏ qua
class SanPham { private tensp:string; private gia: number;//usd constructor( t:string, g:number ){ this.tensp=t; this.gia=g; } tienVND(soluong: number, @logTygia tygia:number) { return this.gia*soluong*tygia } } function logTygia(target: Object, methodKey: string, parameterIndex: number) { console.log(target, methodKey, parameterIndex); } var c = new SanPham("Gạo", 5); console.log(c.tienVND(2,20000)); // 200000
Decorator trong TypeScript có nhiều loại và nhiều ứng dụng rộng rãi. Là cách thức hay bạn lập trình javascript smooth hơn. Cần tham khảo thêm thì xem link này nhé: https://www.typescriptlang.org/docs/handbook/decorators.html