Vấn đề
Lỗi này xuất hiện khi bạn cố gắng sử dụng từ khóa new cho một lớp được đánh dấu bằng công cụ sửa đổi abstract. Hãy coi một lớp abstract (lớp trừu tượng) như một bản phác thảo hoặc một bản thiết kế. Nó xác định những gì một nhóm các lớp liên quan nên làm, nhưng bản thân nó không phải là một sản phẩm hoàn chỉnh. Vì nó chưa hoàn thiện, TypeScript ngăn bạn biến nó thành một đối tượng thực tế.
Hãy tưởng tượng bạn đang xây dựng một hệ thống ghi log. Bạn có thể gặp lỗi với đoạn mã như sau:
abstract class Logger {
abstract log(message: string): void;
info(message: string) {
console.log(`[INFO]: ${message}`);
}
}
// ❌ Lỗi TypeScript: Cannot create an instance of an abstract class.
const myLogger = new Logger();
Tại sao lỗi này xảy ra
Từ khóa abstract đóng vai trò như một cơ chế bảo vệ. Vì các lớp abstract thường chứa các phương thức mà không có mã thực thi thực tế (phương thức abstract), việc tạo một instance (thể hiện) sẽ rất nguy hiểm. Nếu TypeScript cho phép điều này, ứng dụng của bạn có khả năng sẽ bị crash ngay khi bạn gọi một phương thức chưa được triển khai lúc runtime. Ngay cả khi lớp của bạn chỉ chứa các phương thức đã được viết đầy đủ, thẻ abstract vẫn báo cho trình biên dịch biết: "Lớp này chỉ dành cho việc kế thừa."
Giải pháp thực tế
1. Triển khai một Lớp con Cụ thể (Concrete Subclass)
Cách khắc phục tiêu chuẩn là tạo một lớp "cụ thể" (concrete class) kế thừa từ lớp abstract cơ sở. Lớp con này sẽ điền vào các chỗ trống bằng cách triển khai bất kỳ logic nào còn thiếu. Đây là cách tiếp cận phổ biến nhất trong 90% các dự án TypeScript hướng đối tượng.
abstract class Shape {
abstract getArea(): number;
}
// ✅ Khắc phục: Tạo một triển khai cụ thể
class Square extends Shape {
constructor(private width: number) {
super();
}
getArea(): number {
return this.width * this.width;
}
}
// Khởi tạo lớp con thay vì lớp cơ sở
const mySquare = new Square(5);
console.log(mySquare.getArea()); // Kết quả: 25
2. Loại bỏ công cụ sửa đổi Abstract
Hãy tự hỏi liệu lớp đó có thực sự cần phải là abstract hay không. Nếu bạn thấy mình muốn sử dụng trực tiếp lớp cơ sở thường xuyên hơn là kế thừa nó, thì từ khóa abstract có lẽ là không cần thiết. Việc loại bỏ nó sẽ biến lớp đó thành một lớp tiêu chuẩn, có thể khởi tạo được.
// Trước: abstract class ApiClient { ... }
// Sau: Chỉ là một lớp thông thường
class ApiClient {
fetchData(endpoint: string) {
return fetch(`https://api.example.com/${endpoint}`);
}
}
const client = new ApiClient(); // Hoạt động hoàn hảo
3. Xử lý Factory Function và Generic
Bạn có thể gặp phải rào cản này khi truyền các lớp vào các factory function hoặc các container Dependency Injection (DI). Nếu một hàm yêu cầu một constructor (hàm khởi tạo) nhưng bạn lại truyền vào một lớp abstract, các kiểu dữ liệu sẽ không khớp. Điều này thường xảy ra trong các framework như NestJS hoặc khi xây dựng các plugin tùy chỉnh.
abstract class BaseService {}
class AuthService extends BaseService {}
// Hàm hỗ trợ này yêu cầu một lớp có thể khởi tạo được
function createInstance<T>(Ctor: new () => T): T {
return new Ctor();
}
// ❌ Lỗi: BaseService là abstract
createInstance(BaseService);
// ✅ Thành công: AuthService là cụ thể
createInstance(AuthService);
Cách kiểm tra việc khắc phục
Đừng chỉ giả định rằng nó đã hoạt động. Hãy làm theo ba bước sau để chắc chắn:
- Kiểm tra IDE của bạn: VS Code sẽ ngay lập tức xóa đường kẻ gợn sóng màu đỏ dưới từ khóa
newsau khi bạn chuyển sang một lớp con cụ thể. - Chạy trình biên dịch: Thực thi lệnh
npx tsctrong terminal của bạn. Nếu quá trình build hoàn tất mà không có lỗi "Cannot create an instance", thì các kiểu dữ liệu của bạn đã ổn. - Kiểm tra logic lúc Runtime: Chạy mã của bạn (ví dụ:
node dist/index.js) để đảm bảo các phương thức của lớp con thực thi logic đúng như mong đợi.
Mẹo thiết kế cho tương lai
Để giữ cho kiến trúc của bạn sạch sẽ, hãy cân nhắc ba mẫu (pattern) sau:
- Interface so với Abstract Class: Nếu bạn chỉ cần xác định cấu trúc mà không có bất kỳ mã dùng chung nào, hãy sử dụng
interface. Nó sẽ biến mất khi runtime, giúp gói ứng dụng (bundle) của bạn nhỏ hơn. - Tiền tố "Base": Việc đặt tên các lớp là
BaseRepositoryhoặcBaseControllercung cấp một dấu hiệu trực quan rõ ràng cho các lập trình viên khác rằng lớp này không được thiết kế để sử dụng độc lập. - Quyền truy cập Constructor: Ngay cả trong các lớp abstract, bạn có thể sử dụng
protected constructor(). Điều này đảm bảo chỉ các lớp con mới có thể gọisuper(), giúp củng cố hành vi abstract ở cấp độ ngôn ngữ.

