Nhảy tới nội dung

Conditional Types

Conditional Types được gọi bằng nhiều tên như kiểu điều kiện, phân nhánh kiểu, v.v. Nó được viết dưới dạng T extends U ? X : Y giống như toán tử ba ngôi, sử dụng ?:. Nếu T có thể gán cho U, kết quả sẽ là X, ngược lại sẽ là Y.

Trong trường hợp này, kiểu sẽ là true:

ts
type IsString<T> = T extends string ? true : false;
 
const a: IsString<"a"> = true;
const a: true
ts
type IsString<T> = T extends string ? true : false;
 
const a: IsString<"a"> = true;
const a: true

Ví dụ, có một utility type Readonly<T> biến các property của object type thành read-only. Readonly<T> chỉ biến các property trực tiếp của object thành read-only, nhưng không áp dụng cho các property của object lồng nhau. Giả sử có object như sau:

ts
type Person = {
name: string;
age: number;
address: {
country: string;
city: string;
};
};
ts
type Person = {
name: string;
age: number;
address: {
country: string;
city: string;
};
};

Khi sử dụng Readonly<Person>, property address tự nó đã là read-only và không thể ghi đè, nhưng các property countrycity của address không phải read-only và có thể ghi đè.

ts
const kimberley: Readonly<Person> = {
name: "Kimberley",
age: 24,
address: {
country: "Canada",
city: "Vancouver",
},
};
 
kimberley.name = "Kim";
Cannot assign to 'name' because it is a read-only property.2540Cannot assign to 'name' because it is a read-only property.
kimberley.age = 25;
Cannot assign to 'age' because it is a read-only property.2540Cannot assign to 'age' because it is a read-only property.
kimberley.address = {
Cannot assign to 'address' because it is a read-only property.2540Cannot assign to 'address' because it is a read-only property.
country: "United States",
city: "Seattle",
};
kimberley.address.country = "United States";
kimberley.address.city = "Seattle";
ts
const kimberley: Readonly<Person> = {
name: "Kimberley",
age: 24,
address: {
country: "Canada",
city: "Vancouver",
},
};
 
kimberley.name = "Kim";
Cannot assign to 'name' because it is a read-only property.2540Cannot assign to 'name' because it is a read-only property.
kimberley.age = 25;
Cannot assign to 'age' because it is a read-only property.2540Cannot assign to 'age' because it is a read-only property.
kimberley.address = {
Cannot assign to 'address' because it is a read-only property.2540Cannot assign to 'address' because it is a read-only property.
country: "United States",
city: "Seattle",
};
kimberley.address.country = "United States";
kimberley.address.city = "Seattle";

Để giải quyết vấn đề này, cần áp dụng Readonly<T> một cách đệ quy. Trong trường hợp này, chúng ta kết hợp Mapped Types và Conditional Types.

ts
type Freeze<T> = Readonly<{
[P in keyof T]: T[P] extends object ? Freeze<T[P]> : T[P];
}>;
ts
type Freeze<T> = Readonly<{
[P in keyof T]: T[P] extends object ? Freeze<T[P]> : T[P];
}>;

Hãy tạo Freeze<T> như thế này và thử sử dụng nó.

ts
const kimberley: Freeze<Person> = {
name: "Kimberley",
age: 24,
address: {
country: "Canada",
city: "Vancouver",
},
};
 
kimberley.name = "Kim";
Cannot assign to 'name' because it is a read-only property.2540Cannot assign to 'name' because it is a read-only property.
kimberley.age = 25;
Cannot assign to 'age' because it is a read-only property.2540Cannot assign to 'age' because it is a read-only property.
kimberley.address = {
Cannot assign to 'address' because it is a read-only property.2540Cannot assign to 'address' because it is a read-only property.
country: "United States",
city: "Seattle",
};
kimberley.address.country = "United States";
Cannot assign to 'country' because it is a read-only property.2540Cannot assign to 'country' because it is a read-only property.
kimberley.address.city = "Seattle";
Cannot assign to 'city' because it is a read-only property.2540Cannot assign to 'city' because it is a read-only property.
ts
const kimberley: Freeze<Person> = {
name: "Kimberley",
age: 24,
address: {
country: "Canada",
city: "Vancouver",
},
};
 
kimberley.name = "Kim";
Cannot assign to 'name' because it is a read-only property.2540Cannot assign to 'name' because it is a read-only property.
kimberley.age = 25;
Cannot assign to 'age' because it is a read-only property.2540Cannot assign to 'age' because it is a read-only property.
kimberley.address = {
Cannot assign to 'address' because it is a read-only property.2540Cannot assign to 'address' because it is a read-only property.
country: "United States",
city: "Seattle",
};
kimberley.address.country = "United States";
Cannot assign to 'country' because it is a read-only property.2540Cannot assign to 'country' because it is a read-only property.
kimberley.address.city = "Seattle";
Cannot assign to 'city' because it is a read-only property.2540Cannot assign to 'city' because it is a read-only property.

Khác với Readonly<T>, address.countryaddress.city giờ đây không thể ghi đè. Đó là vì Freeze<T> được áp dụng một cách đệ quy.

Phần [P in keyof T] đã được giải thích trong trang Mapped Types nên ở đây sẽ giải thích ngắn gọn. keyof T chuyển đổi các key của object thành union type. Với kimberley, nó sẽ là "name" | "age" | "address". in có nghĩa là một trong số đó.
T[P] lấy kiểu của property tại một key nào đó của object. Nếu kiểu đó là object, áp dụng đệ quy Freeze<T[P]>, ngược lại sử dụng T[P] như vốn có.

📄️ Mapped Types

Với index type, bạn có thể tự do thiết lập bất kỳ key nào khi gán giá trị, nhưng khi truy cập phải kiểm tra undefined mỗi lần. Nếu format input đã được xác định rõ ràng, bạn có thể cân nhắc sử dụng Mapped Types.

Nhờ đó, chúng ta có thể đóng băng object một cách đệ quy.