Decorator trong TypeScript hướng dẫn cách tạo và 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
- Thực tập với decorator trong typescript
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": {
"outDir" : "js",
"watch" : true,
"target" : "ES2022",
"removeComments" : true,
"experimentalDecorators": true
},
}
Khai báo decorator
Khai báo decorator đơn giản, bằng cách sử dụng lệnh @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
Loại decorator được chỉ định ngay trước khai báo class. Class decorator gắn vào constructor của class và sử dụng để can thệp vào class như sửa đổi, bổ sung , thay thế một số định nghĩa trong class. Hàm 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.
Ví dụ dùng class decorator
function ThuCungEx(constructor: Function) { console.log("Đây là hàm ThuCung Ex"); } @ThuCungEx class ThuCung { constructor(private ten:string, private tuoi:number){} }

Một ví dụ khác dùng class decorator
Dùng class decorator để thêm thuộc tính
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
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);

Ví dụ dùng class decorator đổi nội dung class
function ChangeHS(constructor: Function):any { return class { private hoten:string; 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
Thực tập với decorator trong typescript
Bài 1: Dùng class decorator
Tham khảo ví dụ này để thực hiện yêu cầu sau
- Khai báo class XeOTo gồm : tenxe, giaxe, mauxe (enum với các giá trị tùy ý)
- Tạo hàm class decorator để bổ sung 2 thuộc tính soluotmua(number) và ngaysx(string)
- Test: Tạo đối tượng theo class, gán giá trị cho các thuộc tính, xem kết quả
- Mời bạn làm thêm: hiện ra trong trang web cho đẹp
Bài 2: Dùng property decorator
Tham khảo ví dụ này để thực hiện yêu cầu sau
- Khai báo class User gồm 2 thuộc tính username, password
- Định nghĩa hàm property decorator có tên TheoDoiPass với 2 tham sớ là sokytumin và sokytumax. Hàm báo lỗi nếu độ dài pass < sokytumin hoặc >sokytumax.
- Gắn hàm vừa định nghĩa vào thuộc tính password với tham số TheoDoiPass(7,20)
- Tạo đối tượng và gán thuộc tính, hiện kết quả
- Mời bạn làm thêm: hiện ra trong trang web cho đẹp
Bài 3: Dùng method decorator để kiểm soát method trong class
Tham khảo ví dụ này để thực hiện yêu cầu sau
a. Tạo class SinhVienThi có thuộc tính diem (number) và hàm hienketqua (nội dung tùy ý) có dùng method decorator cho hàm này với 2 tham số 0, 10
b. Định nghĩa hàm kiemtradiem với 2 tham số min,max
Kiểm tra điểm >=max thi gán là max, nếu diem<min thì gán làm min
function kiemtradiem( min: number, max:number) {
…
}
c. Cải thiện thêm: Bổ sung method khác và decorator cho method
Bài 4: Dùng decorator factory
Hãy cho một ví dụ dùng decorator factory