我想做一个通用的事件处理程序,我希望能够指定像“pointermove”这样的事件键,并通过类型脚本来推断事件类型,在本例中是PointerEvent,但是当使用多个事件时,我会得到一个错误。
这里有一个最小可重复的例子
export type ContainedEvent< K extends keyof HTMLElementEventMap> = {
eventName: K;
callback: ContainedEventCallback< K>;
};
export type ContainedEventCallback< K extends keyof HTMLElementEventMap> = (
event: HTMLElementEventMap[K],
) => void;
export default function useContainedMultiplePhaseEvent<
K extends keyof HTMLElementEventMap = keyof HTMLElementEventMap
>(
el: HTMLElement ,
events: ContainedEvent<K>[],
) {
for (const e of events) {
el.addEventListener(e.eventName, (ev) => e.callback(ev));
}
}
const div = document.createElement("div");
const doA: ContainedEventCallback<"pointerdown"> = (
e,
) => {
console.log("A")
};
const doB: ContainedEventCallback<"pointermove"> = (
e,
) => {
console.log("B")
};
useContainedMultiplePhaseEvent(div,
[
{
eventName: "pointerdown",
callback: doA,
},
{
eventName: "pointermove",
callback: doB,
}
]
);
1条答案
按热度按时间zujrkrfu1#
我认为这里的主要问题是,当TypeScript从数组字面量推断genericelement 类型时,它只查询数组的第一个元素。这通常是人们想要的,因为它允许像
function foo<T>(...args: T[]) {}
这样的东西接受foo("a", "b", "c")
和foo(1, 2, 3)
,但拒绝foo("a", 2, "c")
。也就是说,当从T[]
类型的值推断T
时,编译器将仅推断 homogeneous 数组。但是在你的例子中,你想允许一个 hetereogenous 数组。在这种情况下,通常的方法是更改泛型类型参数,使其引用整个数组,而不仅仅是数组的元素类型。在您的例子中,这意味着不是让
K
引用ContainedEvent
的类型参数,而是让它引用ContainedEvent
s元组的类型参数的元组。这意味着K
将类似于["pointerdown", "pointermove"]
,而events
将是[ContainedEvent<"pointerdown">, ContainedEvent<"pointermove">]
类型。像这样:
因此
events
的类型是Map的元组类型,其中K[I]
的类数字索引I
处的K
的每个元素用ContainedEvent
Package 以得到ContainedEvent<K[I]>
。哦,events
的类型被 Package 在可变元组类型[...⋯]
中,以给予编译器我们希望events
的类型被推断为元组而不是无序数组(此行为在ms/TS#39094中描述)。让我们试试看:
看起来不错!
这就回答了我的问题。
还有其他可能的方法,但我不想偏离您的原始代码太远。由于
ContainedEvent<K>
中的K
实际上只能是固定联合keyof HTMLElementEventMap
中的一个,因此您实际上可以将ContainedEvent
本身变成一个联合,可能是通过将定义更改为ms/TS#47109中描述的 * 分布式对象类型 *。那么你的useContainedMultiplePhaseEvent
函数就不需要是泛型的,因为events
的每个元素都是union类型ContainedEvent
。它看起来像这样:其也如所期望的那样工作。您可以查看ms/TS#47109,了解分布式对象类型的工作原理;否则我就不离题在这里详细解释了。
Playground链接到代码