網站搜尋

Typescript 為什麼這個遞歸映射類型不符合約束條件?


type RecursiveMap<
    A extends Record<string, unknown>,
    B extends Record<string, unknown>
> = {
    [K in keyof A]: K extends keyof B
        ? A[K] extends Record<string, unknown>
            ? B[K] extends Record<string, unknown>
                ? RecursiveMap<A[K], B[K]>
                : never
            : never
        : never
}

遊樂場

為什麼 A[K] 不符合這個約束條件?我已經用 A[K] extends Record<string, unknown> 檢查過了,這怎麼說得通呢?

答案#1

這是由於 TypeScript 中的一個錯誤,請參見 microsoft/TypeScript#45651。顯然在 TS4.5 及以下版本中,對索引訪問類型進行的這類條件類型檢查並不總是能正確約束真分支中的類型。幸運的是,這個錯誤已在 microsoft/TypeScript#47791 中修復,該修復應該會包含在即將到來的 TS4.6 版本中,你現在可以使用其候選發布版本 4.6.1-rc:

type RecursiveMap<
    A extends Record<string, unknown>,
    B extends Record<string, unknown>
> = {
    [K in keyof A]: K extends keyof B
        ? A[K] extends Record<string, unknown>
            ? B[K] extends Record<string, unknown>
                ? RecursiveMap<A[K], B[K]> // okay
                : never
            : never
        : never
}

Playground 連結至 4.6.1-rc

如果你等不及修復版本,你可以使用 Extract<T, U> 工具類型的非官方用法來解決這個問題。如果你有一個類型 T,你知道它可以指派給類型 U,但編譯器無法認知,你可以用 Extract<T, U> 來替代 U 並解決這個問題。只要你對 T 和 U 的判斷是正確的,那麼後續 Extract<T, U> 將會評估為 T(如果判斷錯誤,它會評估為更接近 T & U 的東西,可能是 never,所以要小心):

type RecursiveMap<
    A extends Record<string, unknown>,
    B extends Record<string, unknown>
    > = {
        [K in keyof A]: K extends keyof B
        ? A[K] extends Record<string, unknown>
        ? B[K] extends Record<string, unknown>
        ? RecursiveMap<
            Extract<A[K], Record<string, unknown>>,
            Extract<B[K], Record<string, unknown>>
        >
        : never
        : never
        : never
    }

程式碼的 Playground 連結