Lỗi Gặp Phải
Bạn truy cập một thuộc tính, và TypeScript lập tức gạch chân đỏ:
const user = {};
console.log(user.name); // Property 'name' does not exist on type '{}'. ts(2339)
Tương tự xảy ra với giá trị trả về của hàm, phản hồi API, hoặc state trong React:
function getUser() {
return {};
}
const user = getUser();
console.log(user.name); // ts(2339)
TypeScript suy luận kiểu là {} — một object rỗng không có thuộc tính nào. Trình biên dịch từ chối vì không có bằng chứng nào cho thấy name tồn tại.
Nguyên Nhân
TypeScript sử dụng hệ thống kiểu cấu trúc. Một kiểu chỉ chứa những thuộc tính bạn khai báo rõ ràng — không có gì được giả định. Viết {} hoặc trả về một object thuần không có chú thích kiểu, TypeScript sẽ chọn kiểu hẹp nhất có thể suy luận. Mọi truy cập thuộc tính trên kiểu đó đều kích hoạt lỗi ts(2339).
Các nguyên nhân phổ biến nhất:
- Object khởi tạo là
{}rồi mới được điền dữ liệu sau - Hàm được khai báo trả về kiểu object chung chung (
object,{},Record<string, unknown>) - Phản hồi API/JSON được khai báo là
anyrồi bị thu hẹp thành{}ở đâu đó - Thiếu hoặc khai báo sai interface
- Thuộc tính được thêm có điều kiện nhưng không được phản ánh trong kiểu
Cách Sửa 1: Định Nghĩa Interface hoặc Type
Bắt đầu từ đây. Khai báo rõ ràng cấu trúc của object — đây là cách sửa có thể mở rộng lâu dài:
interface User {
name: string;
age?: number; // tùy chọn
}
const user: User = { name: 'Alice' };
console.log(user.name); // OK
Với giá trị trả về của hàm, hãy chú thích kiểu trả về luôn:
function getUser(): User {
return { name: 'Alice' };
}
const user = getUser();
console.log(user.name); // OK
Cách Sửa 2: Ép Kiểu (Type Assertion)
Bạn biết cấu trúc dữ liệu, nhưng TypeScript thì không. Dùng as để thông báo cho nó:
const raw = JSON.parse(response) as { name: string };
console.log(raw.name); // OK
Hoặc khi xây dựng object theo từng bước:
const user = {} as User;
user.name = 'Alice';
console.log(user.name); // OK
Lưu ý: Ép kiểu bỏ qua hoàn toàn việc kiểm tra kiểu. Nếu dữ liệu thực tế không khớp, bạn sẽ gặp lỗi runtime mà không có cảnh báo nào. Chỉ dùng cách này khi bạn chắc chắn về cấu trúc dữ liệu.
Cách Sửa 3: Dùng Type với Index Signature
Khóa động là một vấn đề khác. Nếu bạn không thể biết tên thuộc tính tại thời điểm biên dịch, index signature là giải pháp:
const user: Record<string, unknown> = { name: 'Alice' };
console.log(user['name']); // OK
// Với kiểu giá trị cụ thể hơn:
const config: Record<string, string> = {};
config.theme = 'dark'; // OK
Cách Sửa 4: Thu Hẹp Kiểu với Type Guard
Phản hồi API thường đến với kiểu unknown. Type guard xác minh cấu trúc trước khi bạn truy cập bất kỳ thứ gì:
function hasName(obj: unknown): obj is { name: string } {
return typeof obj === 'object' && obj !== null && 'name' in obj;
}
const data: unknown = JSON.parse(response);
if (hasName(data)) {
console.log(data.name); // TypeScript biết đây là an toàn
}
Cách Sửa 5: Optional Chaining (cho Thuộc Tính Thực Sự Tùy Chọn)
Đôi khi một thuộc tính có thể tồn tại hoặc không — và điều đó là chủ ý. Khai báo nó là tùy chọn trong kiểu, rồi xử lý trường hợp vắng mặt tại nơi gọi:
interface User {
name?: string;
}
const user: User = {};
console.log(user.name ?? 'Anonymous'); // OK
Cần lưu ý: optional chaining đơn thuần không sửa được ts(2339). Thuộc tính vẫn phải xuất hiện trong định nghĩa kiểu, dù được đánh dấu là tùy chọn.
Cách Sửa 6: Mở Rộng Type Có Sẵn
Kiểu của thư viện hiếm khi bao gồm các trường tùy chỉnh của bạn. Hãy mở rộng chúng thay vì định nghĩa lại từ đầu:
// Request của Express với trường auth tùy chỉnh
import { Request } from 'express';
interface AuthRequest extends Request {
user?: { name: string };
}
app.get('/', (req: AuthRequest, res) => {
console.log(req.user?.name); // OK
});
Cách Sửa 7: Dùng Generics cho Hàm Tái Sử Dụng
Generics giải quyết vấn đề tái sử dụng. Ràng buộc tham số kiểu để yêu cầu thuộc tính bạn cần:
function getName<T extends { name: string }>(obj: T): string {
return obj.name; // OK — T được đảm bảo có 'name'
}
getName({ name: 'Alice', age: 30 }); // hoạt động
getName({}); // ts(2345) — bị bắt tại nơi gọi, không bị chôn vùi bên trong hàm
Kiểm Tra Sau Khi Sửa
Chạy trình biên dịch với --noEmit để kiểm tra toàn bộ dự án mà không tạo file output:
npx tsc --noEmit
Không có output lỗi nghĩa là ts(2339) đã được xóa. Trong VS Code, gạch chân đỏ biến mất ngay khi bạn lưu file. Hover chuột lên biến — tooltip phải hiển thị tên interface của bạn, không phải {}.
Phòng Ngừa
- Bật strict mode trong
tsconfig.json:"strict": true. Phát hiện ts(2339) và các lỗi liên quan sớm, trước khi chúng lan rộng thành vấn đề lớn hơn. - Tránh dùng
anycho phản hồi API. Dùngunknownvà thu hẹp với type guard — điều này giữ cho hệ thống kiểu hoạt động trung thực. - Viết kiểu trước khi triển khai. Interface nhanh để định nghĩa và tiết kiệm nhiều thời gian debug về sau.
- Dùng toán tử
satisfies(TypeScript 4.9+) để xác nhận một object khớp với kiểu mà không mở rộng nó:
const user = { name: 'Alice', age: 30 } satisfies User;
console.log(user.name); // OK — kiểu giữ hẹp, không bị mở rộng thành User

