Tìm kiếm trang web

typescript - Keyof các đối tượng con lồng nhau


Tôi có một đối tượng kiểu đệ quy mà tôi muốn lấy các khóa của nó và bất kỳ khóa con nào của một kiểu nhất định.

Ví dụ. Dưới đây tôi muốn lấy một kiểu hợp nhất của:

'/another' | '/parent' | '/child'

Ví dụ :

export interface RouteEntry {
    readonly name: string,
    readonly nested : RouteList | null
}
export interface RouteList {
    readonly [key : string] : RouteEntry
}

export const list : RouteList = {
    '/parent': {
        name: 'parentTitle',
        nested: {
            '/child': {
                name: 'child',
                nested: null,
            },
        },
    },
    '/another': {
        name: 'anotherTitle',
        nested: null
    },
}

Trong TypeScript, bạn có thể sử dụng keyof typeof RouteList để lấy kiểu hợp nhất:

'/another' | '/parent' 

Có phương pháp nào để cũng bao gồm các loại lồng nhau không

Câu trả lời #1

Đây là một giải pháp đệ quy vô hạn:

type Paths<T> = T extends RouteList
  ? keyof T | { [K in keyof T]: Paths<T[K]['nested']> }[keyof T]
  : never

type ListPaths = Paths<typeof list> // -> "/parent" | "/another" | "/child"

Đã được kiểm tra trên Typescript v3.5.1. Bạn cũng cần loại bỏ chú thích kiểu khỏi biến list như được @jcalz khuyên.

Câu trả lời #2

Đó là một vấn đề khó. TypeScript thiếu cả các kiểu điều kiện ánh xạ và các định nghĩa kiểu đệ quy tổng quát, mà cả hai đều là những gì tôi muốn dùng để cung cấp cho bạn kiểu hợp (union type) đó. (Chỉnh sửa 05-04-2019: các kiểu điều kiện đã được giới thiệu trong TS2.8) Có một số điểm khó khăn với những gì bạn muốn:

  • Thuộc tính nested của một RouteEntry đôi khi có thể là null, và các biểu thức kiểu đánh giá thành keyof null hoặc null[keyof null] bắt đầu gây lỗi. Cần phải cẩn thận. Giải pháp tạm thời của tôi bao gồm thêm một khóa giả để nó không bao giờ là null, sau đó xóa nó vào cuối.
  • Bất kể loại bí danh nào bạn sử dụng (gọi nó là RouteListNestedKeys<X>) dường như cần được định nghĩa theo chính nó, và bạn sẽ nhận được lỗi "tham chiếu vòng". Một cách giải quyết là cung cấp thứ gì đó hoạt động đến một mức độ lồng nhau hữu hạn nào đó (ví dụ, sâu 9 cấp). Điều này có thể khiến trình biên dịch chậm đáng kể, vì nó có thể đánh giá tất cả 9 cấp ngay một cách vội vàng thay vì hoãn việc đánh giá đến sau.
  • Điều này cần nhiều thành phần alias kiểu liên quan đến các kiểu ánh xạ, và có một lỗi với việc kết hợp các kiểu ánh xạ mà sẽ không được sửa cho đến TypeScript 2.6. Một giải pháp tạm thời liên quan đến việc sử dụng các tham số kiểu mặc định tổng quát.
  • Bước "gỡ bỏ một khóa giả ở cuối" liên quan đến một phép toán kiểu được gọi là Diff mà cần ít nhất TypeScript 2.4 để chạy đúng cách.

Tất cả những gì điều đó có nghĩa là: Tôi có một giải pháp hoạt động, nhưng tôi cảnh báo bạn, nó phức tạp và điên rồ. Một điều cuối cùng trước khi tôi đưa mã vào: bạn cần thay đổi

export const list: RouteList = { // ...

đến

export const list = { // ...

Điều đó có nghĩa là, hãy loại bỏ chú thích kiểu khỏi biến list. Nếu bạn chỉ định nó là RouteList, bạn sẽ bỏ qua kiến thức của TypeScript về cấu trúc chính xác của list, và bạn sẽ chỉ nhận được string làm kiểu khóa. Bằng cách bỏ qua chú thích, bạn để TypeScript suy diễn kiểu, và do đó nó sẽ ghi nhớ toàn bộ cấu trúc lồng nhau.

Được rồi, bắt đầu nhé:

type EmptyRouteList = {[K in 'remove_this_value']: RouteEntry};
type ValueOf<T> = T[keyof T];
type Diff<T extends string, U extends string> = ({[K in T]: K} &
  {[K in U]: never} & { [K: string]: never })[T];
type N0<X extends RouteList> = keyof X
type N1<X extends RouteList, Y = {[K in keyof X]: N0<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y> 
type N2<X extends RouteList, Y = {[K in keyof X]: N1<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y> 
type N3<X extends RouteList, Y = {[K in keyof X]: N2<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y> 
type N4<X extends RouteList, Y = {[K in keyof X]: N3<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y> 
type N5<X extends RouteList, Y = {[K in keyof X]: N4<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y> 
type N6<X extends RouteList, Y = {[K in keyof X]: N5<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y> 
type N7<X extends RouteList, Y = {[K in keyof X]: N6<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y> 
type N8<X extends RouteList, Y = {[K in keyof X]: N7<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y> 
type N9<X extends RouteList, Y = {[K in keyof X]: N8<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y> 
type RouteListNestedKeys<X extends RouteList, Y = Diff<N9<X>,'remove_this_value'>> = Y;

Hãy thử nó:

export const list = {
    '/parent': {
        name: 'parentTitle',
        nested: {
            '/child': {
                name: 'child',
                nested: null,
            },
        },
    },
    '/another': {
        name: 'anotherTitle',
        nested: null
    },
}

type ListNestedKeys = RouteListNestedKeys<typeof list> 

Nếu bạn kiểm tra ListNestedKeys bạn sẽ thấy rằng nó là "parent" | "another" | "child", đúng như bạn muốn. Việc đó có xứng đáng hay không là tùy bạn.

Hú! Hy vọng điều đó giúp ích. Chúc may mắn!

Bài viết liên quan