Sửa lỗi TypeScript: Property Does Not Exist on Type (ts2339)

beginner🔵 TypeScript2026-03-19| TypeScript 4.x / 5.x, Node.js, React, Next.js, mọi dự án TypeScript (VS Code, WebStorm, tsc CLI)

Error Message

Property 'name' does not exist on type '{}'. ts(2339)
#typescript#property#type#ts2339

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à any rồ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 any cho phản hồi API. Dùng unknown và 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

Related Error Notes