首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何在包装addEventListener调用中正确键入TypeScript中的事件处理程序

如何在包装addEventListener调用中正确键入TypeScript中的事件处理程序
EN

Stack Overflow用户
提问于 2020-01-28 00:45:00
回答 2查看 7K关注 0票数 8

我试图在addEventListener方法上编写一个抽象层,但遇到了输入问题。在以下最低限度繁殖;

代码语言:javascript
运行
复制
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不兼容:

代码语言:javascript
运行
复制
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.

我知道以下(基本)事件侦听器:

代码语言:javascript
运行
复制
window.addEventListener('mousedown', (event: MouseEvent) => {
  console.log(event.pageX);
});

工作得很好,所以我假设handler参数输入为EventListener是错误的,但我不知道它应该是什么。我遇到的大多数答案似乎都是特定的反应,这与我的情况无关。

上面的代码为:TypeScript游乐场 \ CodeSandbox

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-01-28 01:59:49

问题是,您的addEventListener()类型不知道type参数如何与handler应该接受的Event的子类型相关联。在TypeScript标准库文件lib.dom.d.ts中,为单个EventTarget类型提供了从typehandler的非常特定的映射。例如,Window接口对其addEventListener方法看起来像这样的签名

代码语言:javascript
运行
复制
addEventListener<K extends keyof WindowEventMap>(
  type: K, 
  listener: (this: Window, ev: WindowEventMap[K]) => any, 
  options?: boolean | AddEventListenerOptions
): void;

其中WindowEventMap在此定义,作为type名称和Event类型之间的大型映射。特别是对于mousedown键,MouseEvent。所以当你打电话的时候

代码语言:javascript
运行
复制
window.addEventListener('mousedown', (event: MouseEvent) => {
  console.log(event.pageX);
});

所有操作都正常,因为K被推断为"mousedown",因此handler参数需要MouseEvent

如果没有这类映射信息,类型就会看起来像typestringlistener(event: Event)=>void。但是,正如您已经看到的,您不能简单地将一个(event: MouseEvent)=>void传递给期望一个(event: Event)=>void的参数,因为这样做通常是不安全的。如果您给我一个只接受MouseEvent的函数,我不能将它作为一个接受所有Event的函数。如果我尝试,并且您给我的函数访问类似于pageX属性的东西,我可能会得到一个运行时错误。

函数参数的这种限制是在TypeScript 2.6中作为一个编译器选项添加的。如果您关闭该编译器选项,您的错误应该会消失,但我不建议您这样做。

相反,如果您只想为您的addEventListener函数松开输入,可以在Event子类型中使其泛化,如下所示:

代码语言:javascript
运行
复制
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);
}

然后,您的代码将编译:

代码语言:javascript
运行
复制
addEventListener(window, "mousedown", (event: MouseEvent) => {
  console.log(event.pageX);
});

但请记住:这种类型太松散,无法捕获错误,如下所示:

代码语言:javascript
运行
复制
addEventListener(document.createElement("div"), "oopsie",
  (event: FocusNavigationEvent) => { console.log(event.originLeft); }
);

如果要在HTMLDivElement上直接尝试这一点,就会得到一个错误:

代码语言:javascript
运行
复制
document.createElement("div").addEventListener("oopsie",
  (event: FocusNavigationEvent) => { console.log(event.originLeft); }
); // error! oopsie is not acceptable

这在修复两个type参数之前是不会被解析的

代码语言:javascript
运行
复制
document.createElement("div").addEventListener("blur",
  (event: FocusNavigationEvent) => { console.log(event.originLeft); }
); // error! FocusNavigationEvent is not a FocusEvent

并使用匹配的Event子类型。

代码语言:javascript
运行
复制
document.createElement("div").addEventListener("blur",
  (event: FocusEvent) => { console.log(event.relatedTarget); }
); // okay now

所以,我只能走这么远了。您可以尝试从所有已知的EventTarget类型映射到每个目标的所有正确的type参数,然后对每个EventTarget/type对进行到所有正确的Event子类型的映射,并有一个通用的addEventListener函数.但是它的大小可以与lib.dom.d.ts文件相媲美,而且我不想花太多的时间在这上面。如果您需要类型安全,那么最好不要使用这个特定的抽象。否则,如果您不介意编译器缺少一些不匹配,那么上面的输入可能是一种方法。

好吧,希望这能帮上忙,祝你好运!

操场链接

票数 8
EN

Stack Overflow用户

发布于 2020-05-25 21:12:19

我想出了一个最有效的解决方案。它需要列出所有的EventMap类型,但是在那之后它应该会捕获所有的错误。

代码语言:javascript
运行
复制
// 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

操场连接

操场链接与更多(全部?)EventMap类型

带有编译时间优化的操场链接

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/59940863

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档