簡體   English   中英

是否可以在 TypeScript 中遞歸地推斷泛型類型?

[英]Is it possible to infer generic types recursively in TypeScript?

以這段代碼為例:

const [divEl, spanEl] = createElements([
    ['div', { id: 'test' }, [
        ['a', { href: 'test' }, [
            ['img', { src: 'test' }, null]
        ]],
        ['img', { href: 'test' }, null]
    ]],
    ['span', null, null]
]);

我希望 TypeScript 將 divEl 的類型推斷為divEl ,將HTMLDivElement的類型spanElHTMLSpanElement 而且我還希望它檢查給定的屬性並根據推斷的類型顯示錯誤(例如,它應該在['img', { href: 'test' }, null]上顯示錯誤,因為HTMLImageElement沒有href屬性)。

經過一番研究,這是我目前所擁有的:

type ElementTag = keyof HTMLElementTagNameMap;

type ElementAttributes<T extends ElementTag> = {
    [K in keyof HTMLElementTagNameMap[T]]?: Partial<HTMLElementTagNameMap[T][K]> | null;
};

type ElementArray<T extends ElementTag> = [
    T,
    ElementAttributes<T> | null,
    ElementArray<ElementTag>[] | string | null
];

type MappedElementArray<T> = {
    [K in keyof T]: T[K] extends ElementArray<infer L> ? HTMLElementTagNameMap[L] : never;
};

// This is the signature for the createElements function.
type CreateElements = <T extends ElementArray<ElementTag>[] | []>(
    array: T
) => MappedElementArray<T>;

我必須將ElementArray<ElementTag>[]更改為通用的,但我不確定如何繼續。

這甚至可能嗎?

我知道可以手動完成,但T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, ...並不漂亮。

(部分回答)

TypeScript 不喜歡遞歸類型。 只要您不需要推理,您幾乎可以解決這個問題……但是,由於這里需要推理,我認為不可能讓 TS 邁出最后一步。

您可以擁有以下功能之一:

  1. 將返回類型推斷為具有正確返回類型的元組。
  2. 對傳遞的數組進行深度類型檢查

第一個很容易。 我們只需要createElements中的T來擴展可區分的聯合。 你已經有了這個,但這是一種稍微不同的看待它的方式。

type DiscriminatedElements = {
    // you can, of course, do better than unknown here.
    [K in keyof HTMLElementTagNameMap]: readonly [K, Partial<HTMLElementTagNameMap[K]> | null, unknown]
}[keyof HTMLElementTagNameMap]

type ToElementTypes<T extends readonly DiscriminatedElements[]> = {
    [K in keyof T]: T[K] extends [keyof HTMLElementTagNameMap, any, any] ? HTMLElementTagNameMap[T[K][0]] : never
}

declare const createElements: <T extends readonly DiscriminatedElements[] | []>(
    array: T
) => ToElementTypes<T>

createElements([
    ['span', {}, null]
]) // [HTMLSpanElement]

第二個有點棘手。 正如我之前所說,TS 不喜歡遞歸類型。 參見GH#26980 也就是說,我們可以繞過它來創建一種任意深度的類型進行檢查……但是如果我們嘗試將這種類型與任何推理結合起來,TS 將意識到它可能是無限的。

type DiscriminatedElements = {
    [K in keyof HTMLElementTagNameMap]: readonly [K, Partial<HTMLElementTagNameMap[K]> | null]
}[keyof HTMLElementTagNameMap]

type NumberLine = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
type CreateElementArray<T extends readonly [any, any], N extends number> = T extends readonly [infer A, infer B] ? {
    done: readonly [A, B, null],
    recurse: readonly [A, B, null | readonly CreateElementArray<DiscriminatedElements, NumberLine[N]>[]]
}[N extends 0 ? 'done' : 'recurse'] : never

// Increase up N and NumberLine as required
type ElementItem = CreateElementArray<DiscriminatedElements, 4>

declare const createElements: (
    array: readonly ElementItem[]
) => HTMLElement[];


const [divEl, spanEl] = createElements([
    ['div', { id: 'test' }, [
        ['a', { href: 'test' }, [
            ['img', { src: 'test' }, null]
        ]],
        ['img', { href: 'test' }, null] // error, as expected
    ]],
    ['span', null, null]
]);

我不認為可變元組在這里幫助你。 它們將是該語言的一個很棒的補充,但不要解決您在此處嘗試 model 的問題。

讓您保持兩全其美的解決方案是接受 HTML 元素作為元組中的第三項,並在該數組中簡單地調用createElements

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM