我试图在addEventListener
方法上编写一个抽象层,但遇到了输入问题。在以下最低限度繁殖;
function addEventListener<T extends EventTarget>(
element: T,
type: string,
handler: EventListener
) {
element.addEventListener(type, handler);
}
addEventListener<Window>(window, "mousedown", (event: MouseEvent) => {
console.log(event.pageX);
});
TypeScript抱怨参数类型与MouseEvent不兼容:
Argument of type '(event: MouseEvent) => void' is not assignable to parameter of type 'EventListener'.
Types of parameters 'event' and 'evt' are incompatible.
Type 'Event' is missing the following properties from type 'MouseEvent': altKey, button, buttons, clientX, and 20 more.
我知道以下(基本)事件侦听器:
window.addEventListener('mousedown', (event: MouseEvent) => {
console.log(event.pageX);
});
工作得很好,所以我假设handler
参数输入为EventListener
是错误的,但我不知道它应该是什么。我遇到的大多数答案似乎都是特定的反应,这与我的情况无关。
上面的代码为:TypeScript游乐场 \ CodeSandbox
发布于 2020-01-28 01:59:49
问题是,您的addEventListener()
类型不知道type
参数如何与handler
应该接受的Event
的子类型相关联。在TypeScript标准库文件lib.dom.d.ts
中,为单个EventTarget
类型提供了从type
到handler
的非常特定的映射。例如,Window
接口对其addEventListener
方法看起来像这样的签名
addEventListener<K extends keyof WindowEventMap>(
type: K,
listener: (this: Window, ev: WindowEventMap[K]) => any,
options?: boolean | AddEventListenerOptions
): void;
其中WindowEventMap
是在此定义,作为type
名称和Event
类型之间的大型映射。特别是对于mousedown
键,MouseEvent
。所以当你打电话的时候
window.addEventListener('mousedown', (event: MouseEvent) => {
console.log(event.pageX);
});
所有操作都正常,因为K
被推断为"mousedown"
,因此handler
参数需要MouseEvent
。
如果没有这类映射信息,类型就会看起来像type
是string
,listener
是(event: Event)=>void
。但是,正如您已经看到的,您不能简单地将一个(event: MouseEvent)=>void
传递给期望一个(event: Event)=>void
的参数,因为这样做通常是不安全的。如果您给我一个只接受MouseEvent
的函数,我不能将它作为一个接受所有Event
的函数。如果我尝试,并且您给我的函数访问类似于pageX
属性的东西,我可能会得到一个运行时错误。
函数参数的这种限制是在TypeScript 2.6中作为一个编译器选项添加的。如果您关闭该编译器选项,您的错误应该会消失,但我不建议您这样做。
相反,如果您只想为您的addEventListener
函数松开输入,可以在Event
子类型中使其泛化,如下所示:
function addEventListener<T extends EventTarget, E extends Event>(
element: T, type: string, handler: (this: T, evt: E) => void) {
element.addEventListener(type, handler as (evt: Event) => void);
}
然后,您的代码将编译:
addEventListener(window, "mousedown", (event: MouseEvent) => {
console.log(event.pageX);
});
但请记住:这种类型太松散,无法捕获错误,如下所示:
addEventListener(document.createElement("div"), "oopsie",
(event: FocusNavigationEvent) => { console.log(event.originLeft); }
);
如果要在HTMLDivElement
上直接尝试这一点,就会得到一个错误:
document.createElement("div").addEventListener("oopsie",
(event: FocusNavigationEvent) => { console.log(event.originLeft); }
); // error! oopsie is not acceptable
这在修复两个type
参数之前是不会被解析的
document.createElement("div").addEventListener("blur",
(event: FocusNavigationEvent) => { console.log(event.originLeft); }
); // error! FocusNavigationEvent is not a FocusEvent
并使用匹配的Event
子类型。
document.createElement("div").addEventListener("blur",
(event: FocusEvent) => { console.log(event.relatedTarget); }
); // okay now
所以,我只能走这么远了。您可以尝试从所有已知的EventTarget
类型映射到每个目标的所有正确的type
参数,然后对每个EventTarget
/type
对进行到所有正确的Event
子类型的映射,并有一个通用的addEventListener
函数.但是它的大小可以与lib.dom.d.ts
文件相媲美,而且我不想花太多的时间在这上面。如果您需要类型安全,那么最好不要使用这个特定的抽象。否则,如果您不介意编译器缺少一些不匹配,那么上面的输入可能是一种方法。
好吧,希望这能帮上忙,祝你好运!
发布于 2020-05-25 21:12:19
我想出了一个最有效的解决方案。它需要列出所有的EventMap
类型,但是在那之后它应该会捕获所有的错误。
// List all `EventMap` types here.
type DOMEventMapDefinitions = [
[HTMLElement, HTMLElementEventMap],
[Document, DocumentEventMap],
[Window, WindowEventMap],
[FileReader, FileReaderEventMap],
[Element, ElementEventMap],
[Animation, AnimationEventMap],
[EventSource, EventSourceEventMap],
[AbortSignal, AbortSignalEventMap],
[AbstractWorker, AbstractWorkerEventMap]
// ...
];
type DOMEventSubscriber = DOMEventMapDefinitions[number][0];
type MapDefinitionToEventMap<D extends { [K: number]: any[] }, T> = { [K in keyof D]: D[K] extends any[] ? (T extends D[K][0] ? D[K][1] : never) : never };
type GetDOMEventMaps<T extends DOMEventSubscriber> = MapDefinitionToEventMap<DOMEventMapDefinitions, T>;
type MapEventMapsToKeys<D extends { [K: number]: any }> = { [K in keyof D]: D[K] extends never ? never : keyof D[K] };
type MapEventMapsToEvent<D extends { [K: number]: any }, T extends PropertyKey> = { [K in keyof D]: D[K] extends never ? never : (T extends keyof D[K] ? D[K][T] : never) };
// Works for all types listed in `DOMEventMapDefinitions` and any types that are assingable to those types.
function customAddEventListener<T extends DOMEventSubscriber, K extends MapEventMapsToKeys<GetDOMEventMaps<T>>[number] & string>(
element: T,
type: K,
handler: (this: T, ev: (MapEventMapsToEvent<GetDOMEventMaps<T>, K>[number])) => any
) {
element.addEventListener(type, handler as any);
}
//--------------------------------------------------------------------------
// Examples:
window.addEventListener('mousedown', (event: MouseEvent) => {
console.log(event.pageX);
});
customAddEventListener(window, "mousedown", (event: MouseEvent) => {
console.log(event.pageX);
});
document.createElement("div").addEventListener("oopsie",
(event: FocusNavigationEvent) => { console.log(event.originLeft); }
); // error! oopsie is not acceptable
customAddEventListener(document.createElement("div"), "oopsie",
(event: FocusNavigationEvent) => { console.log(event.originLeft); }
); // error! Argument of type '"oopsie"' is not assignable ...
document.createElement("div").addEventListener("blur",
(event: FocusNavigationEvent) => { console.log(event.originLeft); }
); // error! FocusNavigationEvent is not a FocusEvent
customAddEventListener(document.createElement("div"), "blur",
(event: FocusNavigationEvent) => { console.log(event.originLeft); }
); // error! 'FocusEvent' is not assignable to type 'FocusNavigationEvent'
document.createElement("div").addEventListener("blur",
(event: FocusEvent) => { console.log(event.relatedTarget); }
); // okay now
customAddEventListener(document.createElement("div"), "blur",
(event: FocusEvent) => { console.log(event.relatedTarget); }
); // okay now
https://stackoverflow.com/questions/59940863
复制相似问题