Tại sao lỗi này xảy ra
Có thể bạn đang gặp lỗi này vì đang sử dụng ECMAScript Private Fields (tiền tố #). Khác với từ khóa private tiêu chuẩn trong TypeScript, cú pháp # thực thi "bảo mật cứng" (hard privacy). Điều này có nghĩa là chính JavaScript engine sẽ chặn truy cập ngay tại thời điểm thực thi (runtime).
Các nguyên nhân phổ biến gây ra lỗi TS18013 bao gồm:
- Truy cập
#fieldtừ một instance bên ngoài định nghĩa class. - Cố gắng đọc
#fieldcủa class cha từ một class con (subclass). - Cố gắng vượt qua cơ chế bảo mật bằng
(obj as any).#field, dẫn đến lỗi cú pháp.
Giải pháp 1: Hiển thị dữ liệu thông qua Getter
Cách sửa lỗi đáng tin cậy nhất là định nghĩa một getter công khai (public). Cách này giúp bảo vệ trạng thái nội bộ trong khi vẫn cung cấp một "cửa sổ" được kiểm soát để mã bên ngoài có thể đọc giá trị.
class User {
#apiToken: string;
constructor(token: string) {
this.#apiToken = token;
}
// Cung cấp chế độ xem chỉ đọc cho dữ liệu
get maskedToken(): string {
return `${this.#apiToken.substring(0, 4)}****`;
}
}
const admin = new User("secret_9921_token");
// console.log(admin.#apiToken); // Lỗi TS18013
console.log(admin.maskedToken); // Hoạt động: "secr****"
Giải pháp 2: Sử dụng từ khóa 'private' cho bảo mật "mềm"
Nếu bạn chỉ cần ngăn chặn việc sử dụng vô ý nhưng muốn giữ cho mã linh hoạt để unit test, hãy thay thế # bằng từ khóa private. private của TypeScript là bảo mật "mềm" — nó kiểm tra lỗi trong quá trình phát triển nhưng sẽ biến mất sau khi mã được biên dịch sang JavaScript.
class Config {
// Sử dụng 'private' thay vì '#'
private port: number = 8080;
public getUrl() {
return `http://localhost:${this.port}`;
}
}
const cfg = new Config();
// cfg.port; // Lỗi TS, nhưng ít khắt khe hơn #
Giải pháp 3: Chuyển sang 'protected' cho các class con
Các định danh private (#) hoàn toàn ẩn đối với các class con. Nếu một subclass cần truy cập một thuộc tính từ class cha, bạn phải sử dụng từ khóa protected. Lưu ý rằng protected không hoạt động với ký hiệu #.
class Parent {
// #internalValue = 10; // Các class con không thể thấy thuộc tính này
protected sharedValue = 10; // Các class con CÓ THỂ thấy thuộc tính này
}
class Child extends Parent {
logValue() {
console.log(this.sharedValue); // Thành công
}
}
Giải pháp 4: Di chuyển Logic vào trong Class
Đôi khi lỗi này là một gợi ý cho thấy thiết kế class của bạn đang bị "rò rỉ". Thay vì lấy dữ liệu ra ngoài để xử lý, hãy chuyển logic đó vào một method bên trong class. Điều này tuân theo nguyên tắc "Tell, Don't Ask" (Hãy ra lệnh, đừng hỏi) của lập trình hướng đối tượng.
// Nên tránh: Can thiệp sâu vào bên trong class
// if (account.#balance > 100) { ... }
// Tốt hơn: Để class tự quản lý trạng thái của chính nó
class Account {
#balance: number = 500;
public canAfford(amount: number): boolean {
return this.#balance >= amount;
}
}
So sánh nhanh: # và private
Tính năng
Định danh #
Từ khóa private
Mức độ bảo mật
Cứng (Runtime)
Mềm (Compile-time)
Truy cập từ Class con
Không
Không (Dùng protected)
Vượt qua bằng 'any'
Không thể
Có thể
Kết quả JavaScript
Giữ nguyên là #field
Trở thành thuộc tính bình thường
Xác minh và kiểm tra
Để đảm bảo lỗi đã hết, hãy chạy npx tsc để kích hoạt kiểm tra kiểu toàn diện. Nếu bạn đã triển khai một getter hoặc thay đổi phạm vi truy cập thành private, IDE của bạn (như VS Code) sẽ ngay lập tức ngừng hiển thị gạch chân đỏ. Để xác minh lúc runtime, hãy kiểm tra console trình duyệt — việc truy cập các trường # từ bên ngoài sẽ ném ra lỗi SyntaxError ngay cả trước khi mã chạy.
Mẹo chuyên nghiệp
- Sử dụng
#cho các thành phần nội bộ của thư viện khi bạn cần ngăn chặn tuyệt đối người dùng chạm vào các trạng thái nhạy cảm. - Ưu tiên dùng
privatecho phát triển ứng dụng thông thường; nó giúp việc viết unit test dễ dàng hơn đáng kể. - Hãy nhớ rằng các trường
#có tốc độ thực thi chậm hơn một chút trên các engine JS cũ, mặc dù V8 hiện đại (Chrome/Node.js) đã tối ưu hóa chúng rất tốt.

