Bài 4 · Vận dụng · 20 phút
Class trong TypeScript
Biên soạn bởi Nguyễn Anh Tuấn
TypeScript thêm gì cho class của JavaScript: access modifier (public/private/protected), thuộc tính readonly, parameter property gọn gàng, abstract class và implements một interface. Phân biệt private của TS (chỉ lúc biên dịch) với #private thật của JS.
Class trong TypeScript vẫn là class JavaScript mà mèo con đã biết. Cái TS thêm vào là vài tính năng
kiểm tra lúc biên dịch. Đầu tiên là access modifier - kiểm soát ai được
đụng vào field/method: public (mặc định, ai cũng dùng), private (chỉ trong class), protected (trong class và lớp con):
private che field khỏi bên ngoài
class TaiKhoan {
private soDu = 0;
nap(x: number) { this.soDu += x; }
}
const t = new TaiKhoan();
t.nap(100); // OK - method public
t.soDu; // Loi - soDu la private tsc báo
error TS2341: Property 'soDu' is private and only accessible within class 'TaiKhoan'. - ▸public (mặc định): ai cũng truy cập được; private: chỉ trong class; protected: thêm cả lớp con.
- ▸Access modifier giúp đóng gói: lộ ra method an toàn, che state bên trong.
- ▸Đây là kiểm tra lúc biên dịch, không phải khoá lúc chạy (xem Bước 3).
Khai báo field rồi gán this.x = x trong constructor là việc lặp đi
lặp lại. TypeScript cho một lối tắt: đặt access modifier ngay trên tham số constructor, gọi là parameter property - TS tự khai báo field và gán giúp:
meo.ts
class Meo {
constructor(private ten: string) {}
} tsc sinh ra JS (tự khai báo + gán this.ten)
class Meo {
constructor(ten) {
this.ten = ten;
}
} Một dòng private ten: string trong ngoặc đã thay cho ba việc: khai báo
field, nhận tham số, và gán. Class nhiều field nhờ vậy gọn hẳn.
Đây là chỗ hay gây ngộ nhận. private của TypeScript chỉ là giao kèo lúc biên dịch - đúng tinh thần type erasure ở Bài 1:
khi ra JavaScript, field private trở thành một thuộc tính thường, vẫn đọc được lúc chạy:
private bị xoá → đọc được lúc chạy
class A { private secret = 42; }
const a = new A();
console.log((a as any).secret); // lach qua "as any" Kết quả khi chạy
42
JS tsc sinh ra: secret chỉ là thuộc tính thường
class A {
constructor() {
this.secret = 42;
}
} Muốn riêng tư thật sự lúc chạy, dùng #private của chính JavaScript - engine tự chặn, không lách được:
#private: riêng tư thật, engine enforce
class B {
#secret = 42;
doc() { return this.#secret; }
}
const b = new B();
console.log(b.doc()); // 42 - method trong class doc duoc
// console.log(b.#secret); // Loi: khong doc duoc tu ngoai lop Kết quả khi chạy
42
Trung thực
Một class có thể hứa "tôi sẽ có hình dạng của interface này" bằng implements - chính là structural typing ở Bài 3 nhưng có tên gọi rõ ràng. Thiếu field thì tsc bắt ngay:
class hứa theo interface
interface CoTen { ten: string; }
class Meo implements CoTen {
constructor(public ten: string) {} // du 'ten' -> OK
} abstract class là khung dùng chung: nó có thể chứa method đã cài đặt
lẫn method abstract (bắt lớp con cài), và không
tạo instance trực tiếp được:
abstract.ts
abstract class HinhHoc {
abstract dienTich(): number; // lop con bat buoc cai dat
moTa() { return "Dien tich: " + this.dienTich(); }
}
class HinhVuong extends HinhHoc {
constructor(private canh: number) { super(); }
dienTich() { return this.canh * this.canh; }
}
// new HinhHoc() -> Loi: Cannot create an instance of an abstract class - ▸implements: class hứa khớp hình dạng một interface; thiếu thì tsc báo TS2420.
- ▸abstract class: khung chung có code dùng lại + method abstract bắt lớp con cài đặt.
- ▸Không new được abstract class trực tiếp (TS2511) - phải qua một lớp con cụ thể.
Bạn vừa thấy TypeScript khoác cho class JavaScript một lớp kiểm tra: che state bằng access modifier, viết gọn bằng parameter property, ràng buộc bằng implements và abstract - tất cả đều là chuyện lúc biên dịch. Từ bài sau, ta quay lại trọng tâm của TypeScript: mô hình hoá dữ liệu bằng kiểu.
Bài tiếp theo: Union & narrowing
Câu hỏi thường gặp
Vẫn là class của JavaScript. TypeScript chỉ thêm các tính năng kiểm tra lúc biên dịch: access modifier (public/private/protected), readonly, parameter property, abstract, implements. Khi biên dịch ra JS, các phần "chỉ để kiểm kiểu" này bay đi, còn lại đúng class JavaScript.
Không, về mặt runtime. private chỉ được kiểm lúc biên dịch rồi bị xoá (type erasure, như Bài 1) - trong JavaScript sinh ra nó là một thuộc tính thường, đọc được bằng cách lách. Muốn riêng tư THẬT lúc chạy, hãy dùng #private của JavaScript (engine tự enforce).
Nó gộp ba việc lặp đi lặp lại làm một: khai báo field, nhận tham số, và gán this.x = x. Viết private ten: string ngay trong dấu ngoặc constructor là TypeScript tự làm hết. Code gọn hơn nhiều khi class có nhiều field.
Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.
Trả lời vài câu để chắc rằng em đã nắm bài.
TypeScript thêm gì cho class so với JavaScript?
- 1
Bọc state bằng private
Viết một class TaiKhoan có field private soDu và method nap, rut. Thử truy cập soDu từ ngoài class xem tsc báo gì.
✅ Hoàn thành khi: Class che được soDu (chỉ method trong class dùng nó), và ghi lại lỗi tsc khi truy cập từ ngoài (TS2341).
- 2
Rút gọn bằng parameter property
Viết một class có 3 field gán trong constructor theo cách dài dòng, rồi viết lại bằng parameter property. So số dòng.
✅ Hoàn thành khi: Hai phiên bản cùng hành vi; bản parameter property ngắn hơn rõ rệt, có ghi chú số dòng tiết kiệm.
- 3
private không phải khoá runtime
Tạo một class có private field, biên dịch ra .js, mở file .js xem field đó còn không. Rồi thử đọc field qua ép kiểu as any.
✅ Hoàn thành khi: Bằng chứng: field private vẫn nằm trong .js như thuộc tính thường và đọc được lúc chạy qua as any.
- 4
Riêng tư thật bằng #private
Viết lại class trên dùng #private của JavaScript thay cho private. Thử đọc #field từ ngoài class.
✅ Hoàn thành khi: Method trong class đọc #field bình thường, nhưng đọc từ ngoài class bị báo lỗi - riêng tư thật sự.
- 5
Hứa bằng implements
Định nghĩa một interface, rồi viết một class implements nó. Cố tình bỏ sót một field để xem tsc bắt lỗi.
✅ Hoàn thành khi: Class đủ field thì biên dịch sạch; thiếu field thì tsc báo "incorrectly implements" (TS2420).
- 6
Khung chung bằng abstract
Viết một abstract class có một method abstract và một method thường dùng tới nó. Viết một lớp con cài đặt method abstract. Thử new thẳng abstract class.
✅ Hoàn thành khi: Lớp con chạy được; new abstract class bị tsc chặn (TS2511), kèm một câu vì sao abstract không tạo instance trực tiếp.