Recursive conditional type là kiểu điều kiện tự gọi lại chính nó, để đi hết mọi tầng của một cấu trúc lồng nhau.
typescript
// biến mọi thuộc tính ở MỌI tầng thành optional
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T
type Config = { db: { host: string; port: number }; debug: boolean }
type Patch = DeepPartial<Config>
// { db?: { host?: string; port?: number }; debug?: boolean }typescript
// làm phẳng mảng lồng nhau
type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T
type X = Flatten<number[][][]> // number
// bóc Promise lồng nhau — chính là cách Awaited<T> hoạt động
type Unwrap<T> = T extends Promise<infer V> ? Unwrap<V> : T
type Y = Unwrap<Promise<Promise<string>>> // stringHai giới hạn cần biết:
1. TypeScript giới hạn độ sâu đệ quy (khoảng 50 tầng), vượt quá sẽ báo Type instantiation is excessively deep and possibly infinite.
2. T extends object cũng đúng với mảng và hàm — nên DeepPartial ở trên sẽ biến đổi cả phần tử mảng theo cách có thể ngoài ý muốn. Bản dùng được trong thực tế phải loại trừ trước:
typescript
type DeepPartial<T> = T extends (infer U)[]
? DeepPartial<U>[]
: T extends Function
? T
: T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: TChốt: đây là công cụ mạnh nhưng làm chậm việc kiểm tra kiểu và khiến thông báo lỗi rất khó đọc.
Dùng cho tiện ích dùng lại nhiều nơi, đừng dùng cho một chỗ duy nhất.