Menu

Kiểu object trong TypeScript: thuộc tính tùy chọn, readonly

Cách khai báo kiểu cho object trong TypeScript: kiểu object viết trực tiếp, thuộc tính tùy chọn với ?, thuộc tính readonly, object lồng nhau, phương thức, kiểm tra thuộc tính thừa, và sự khác nhau giữa object, {} và Object.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

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ểuChấp nhậnTừ chối
objectmọi giá trị không phải kiểu nguyên thủy: {}, [], hàm, instance của lớp5, "a", true, null, undefined
{}mọi giá trị trừ null và undefined, kể cả kiểu nguyên thủynull, undefined
Objectgiố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íchnull, 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.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu