[英]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
的类型spanEl
为HTMLSpanElement
。 而且我还希望它检查给定的属性并根据推断的类型显示错误(例如,它应该在['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 迈出最后一步。
您可以拥有以下功能之一:
第一个很容易。 我们只需要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.