Kiểu object trong TypeScript liệt kê các thuộc tính mà object có và kiểu của từng thuộc tính: { name: string; age: number }. Thêm ? để thuộc tính trở thành tùy chọn và readonly để không cho gán lại nó. Bạn có thể viết kiểu trực tiếp, hoặc đặt tên bằng type hay interface để dùng lại.
Viết kiểu object
Các thuộc tính được ngăn cách bằng ; hoặc , (cả hai đều được, ; là cách viết thường gặp), và chỉ cần xuống dòng cũng đủ. Kiểu viết trực tiếp phù hợp cho một tham số dùng một lần; với thứ gì dùng từ hai lần trở lên, hãy đặt tên cho nó.
// Inline, in a parameter
function area(rect: { width: number; height: number }): number {
return rect.width * rect.height;
}
// Named with a type alias
type Rect = { width: number; height: number };
// Named with an interface (the same shape)
interface RectShape {
width: number;
height: number;
}
type và interface mô tả cấu trúc object tốt như nhau. Những điểm khác biệt (gộp khai báo, union) được trình bày ở trang interface và type.
Truy cập một thuộc tính mà kiểu không khai báo là lỗi biên dịch: point.z gây lỗi TS2339, Property 'z' does not exist on type '{ x: number; y: number; }'.
Thuộc tính tùy chọn
Dấu ? sau tên cho phép bỏ qua thuộc tính. Đọc một thuộc tính tùy chọn sẽ nhận T | undefined, nên TypeScript buộc bạn xử lý trường hợp thiếu trước khi dùng.
Gọi phương thức trên một thuộc tính tùy chọn mà không kiểm tra là lỗi: p.nickname.toUpperCase() thất bại với TS18048, 'p.nickname' is possibly 'undefined'. Dùng optional chaining (p.nickname?.toUpperCase()) khi kết quả undefined là chấp nhận được.
prop?: T và prop: T | undefined không giống nhau. Cách đầu cho phép khóa vắng mặt; cách sau bắt buộc có khóa, dù giá trị có thể là undefined:
Thuộc tính readonly
readonly ngăn gán lại một thuộc tính sau khi object đã được tạo. Đây chỉ là kiểm tra lúc biên dịch, và nó chỉ ở mức nông: object hay mảng lưu trong một thuộc tính readonly vẫn có thể bị thay đổi bên trong.
Đầu ra cho thấy cả hai giới hạn: id thực sự đã thay đổi lúc chạy (chỉ trình biên dịch biết nó là readonly), và mảng bên trong đã bị sửa. Với mảng chỉ đọc, dùng readonly string[]; để mọi thuộc tính đều readonly cùng lúc, dùng Readonly<Order>.
Kiểm tra thuộc tính thừa
Khi bạn gán trực tiếp một object literal cho biến có kiểu, hoặc truyền thẳng nó vào tham số có kiểu, TypeScript từ chối mọi thuộc tính mà kiểu không khai báo. Thuộc tính thừa trong một literal mới gần như luôn là lỗi gõ nhầm.
index.ts(8,8): error TS2561: Object literal may only specify known properties, but 'colour' does not exist in type 'Options'. Did you mean to write 'color'?
Mã lỗi là TS2561 vì trình biên dịch tìm thấy một tên gần giống; thuộc tính thừa không có tên tương tự sẽ gây TS2353, Object literal may only specify known properties, and 'z' does not exist in type 'Point'. Nếu không có phép kiểm tra này, lỗi gõ nhầm vẫn biên dịch được, color sẽ là undefined, và chương trình lặng lẽ vẽ bằng màu đen. Phép kiểm tra chỉ áp dụng cho literal mới. Một object đã nằm trong biến có thể mang thuộc tính thừa, vì hệ thống kiểu của TypeScript là structural: một giá trị khớp với kiểu khi nó có ít nhất các thuộc tính bắt buộc.
Object lồng nhau và phương thức
Kiểu object có thể lồng nhau, và có thể mô tả phương thức bằng cú pháp phương thức hoặc bằng một thuộc tính có kiểu hàm.
Với cấu trúc sâu hoặc được dùng lại, hãy đặt tên cho kiểu bên trong (type Address = { ... }) rồi tham chiếu đến nó, hoặc lấy nó ra từ kiểu bên ngoài bằng indexed access, Company["address"], như các dòng cuối.
object, {} và Object
Ba kiểu nghe giống nhau nhưng mang nghĩa khác nhau:
| Kiểu | Chấp nhận | Từ chối |
|---|---|---|
object | mọi giá trị không phải kiểu nguyên thủy: {}, [], hàm, instance của lớp | 5, "a", true, null, undefined |
{} | mọi giá trị trừ null và undefined, kể cả kiểu nguyên thủy | null, undefined |
Object | giống {}, cộng thêm kiểm tra các thành phần có sẵn như toString giữ kiểu tương thích | null, undefined |
{ x: number } | mọi giá trị có x kiểu số | giá trị không có x |
{} không có nghĩa là "một object rỗng"; nó có nghĩa là "không phải null hay undefined". Để nhận mọi object với các khóa chưa biết trước, dùng Record<string, unknown>; với một bảng ánh xạ từ khóa sang giá trị, dùng index signature hoặc Record như ở trang từ điển (dictionary). Trong đa số trường hợp, một cấu trúc cụ thể tốt hơn cả ba kiểu trên.
Câu hỏi thường gặp
Khai báo kiểu object trong TypeScript như thế nào?
Liệt kê các thuộc tính và kiểu của chúng trong cặp ngoặc nhọn: { name: string; age: number }. Bạn có thể viết trực tiếp trong chú thích kiểu, hoặc đặt tên bằng type User = { ... } hay interface User { ... } để dùng lại. Ngăn cách các thuộc tính bằng ; hoặc ,.
Làm sao để một thuộc tính trở thành tùy chọn trong TypeScript?
Đặt ? sau tên thuộc tính: { name: string; nickname?: string }. Object có thể không có nickname, và khi đọc sẽ nhận string | undefined, nên bạn phải kiểm tra hoặc đặt giá trị mặc định (user.nickname ?? user.name) trước khi dùng nó như một chuỗi.
prop?: string và prop: string | undefined khác nhau thế nào?
Với prop?: string, khóa có thể bị bỏ qua hoàn toàn. Với prop: string | undefined, khóa là bắt buộc dù giá trị có thể là undefined, nên {} gây lỗi biên dịch. Đọc cả hai đều nhận string | undefined.
object, {} và Object trong TypeScript khác nhau thế nào?
object là mọi giá trị không phải kiểu nguyên thủy (object, mảng, hàm) và từ chối 5 hay "a". {} là mọi giá trị trừ null và undefined, kể cả kiểu nguyên thủy. Object gần giống {} nhưng còn kiểm tra các phương thức có sẵn như toString giữ kiểu tương thích. Hãy dùng object, hoặc tốt hơn là một cấu trúc cụ thể, thay vì {} hay Object.
Vì sao TypeScript báo object literal chỉ được khai báo các thuộc tính đã biết?
Đó là kiểm tra thuộc tính thừa (excess property check): lỗi TS2353, hoặc TS2561 khi trình biên dịch gợi ý được thuộc tính mà bạn có lẽ định viết. Khi bạn gán trực tiếp một object literal mới cho biến hoặc tham số có kiểu, mọi thuộc tính mà kiểu không khai báo đều bị báo lỗi, vì thường đó là lỗi gõ nhầm. Gán một object đã lưu trong biến khác thì bỏ qua phép kiểm tra này, vì structural typing cho phép thuộc tính thừa.