[英]Is it possible to infer generic types recursively in TypeScript?
Take this code, for example:以这段代码为例:
const [divEl, spanEl] = createElements([
['div', { id: 'test' }, [
['a', { href: 'test' }, [
['img', { src: 'test' }, null]
]],
['img', { href: 'test' }, null]
]],
['span', null, null]
]);
I want TypeScript to infer the type of divEl
to be HTMLDivElement
and the type of spanEl
to be HTMLSpanElement
.我希望 TypeScript 将 divEl 的类型推断为
divEl
,将HTMLDivElement
的类型spanEl
为HTMLSpanElement
。 And I also want it to check the given attributes and show errors based on the inferred type (for example, it should show an error on ['img', { href: 'test' }, null]
, because HTMLImageElement
doesn't have an href
property).而且我还希望它检查给定的属性并根据推断的类型显示错误(例如,它应该在
['img', { href: 'test' }, null]
上显示错误,因为HTMLImageElement
没有href
属性)。
After some research, this is what I have so far:经过一番研究,这是我目前所拥有的:
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>;
I'd have to change ElementArray<ElementTag>[]
to something generic, but I'm not sure how to proceed.我必须将
ElementArray<ElementTag>[]
更改为通用的,但我不确定如何继续。
Is this even possible?这甚至可能吗?
I know it's possible to do it manually, but T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, ...
isn't pretty.我知道可以手动完成,但
T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, ...
并不漂亮。
(Partial answer) (部分回答)
TypeScript doesn't like recursive types. TypeScript 不喜欢递归类型。 You can mostly work around this, so long as you don't require inference... however, since that's required here, I don't think it is possible to get TS to take the last step.
只要您不需要推理,您几乎可以解决这个问题……但是,由于这里需要推理,我认为不可能让 TS 迈出最后一步。
You can have one of the following features:您可以拥有以下功能之一:
The first is easy.第一个很容易。 We just need
T
in createElements
to extend a discriminated union.我们只需要
createElements
中的T
来扩展可区分的联合。 You had this already, but this is a slightly different way of looking at it.你已经有了这个,但这是一种稍微不同的看待它的方式。
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]
The second is a bit trickier.第二个有点棘手。 As I said before, TS doesn't like recursive types.
正如我之前所说,TS 不喜欢递归类型。 See GH#26980 .
参见GH#26980 。 That said, we can work around it to create a type of an arbitrary depth for checking... but if we try to combine this type with any inference, TS will realize it is potentially infinite.
也就是说,我们可以绕过它来创建一种任意深度的类型进行检查……但是如果我们尝试将这种类型与任何推理结合起来,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]
]);
I don't think variadic tuples help you here.我不认为可变元组在这里帮助你。 They will be an awesome addition to the language, but don't solve the problem that you are trying to model here.
它们将是该语言的一个很棒的补充,但不要解决您在此处尝试 model 的问题。
A solution that would let you keep the best of both worlds would be to accept HTML elements as the third item in the tuple, and simply call createElements
within that array.让您保持两全其美的解决方案是接受 HTML 元素作为元组中的第三项,并在该数组中简单地调用
createElements
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.