【问题标题】:How to define JSDoc for TouchEvent handler如何为 TouchEvent 处理程序定义 JSDoc
【发布时间】:2022-01-13 05:43:41
【问题描述】:

我正在使用适当的 JSDoc 更新我的 shorter-js 代码库以生成 TypeScript 定义,但我无法缩小这一范围。

我有on() sn-p,它使用原生Element.addEventListener,到目前为止一切都很好, 问题是当使用TouchEvent 作为实际处理程序的参数时,TypeScript 会抛出 4 行错误,请参见下面的 sn-p。

/**
 * @param {HTMLElement | Element | Document} element event.target
 * @param {string} eventName event.type
 * @param {EventListener} handler callback
 * @param {EventListenerOptions | boolean | undefined} options other event options
 */
function on(element, eventName, handler, options) {
  const ops = options || false;
  element.addEventListener(eventName, handler, ops);
}

/**
 * test handler
 * @type {EventListener}
 * @param {TouchEvent} e
 */
function touchdownHandler(e){
  console.log(e.touches)
}

// test invocation
on(document.body, 'touchdown', touchdownHandler);
body {height: 100%}

on(document.body, 'touchdown', touchdownHandler) 调用会引发以下 4 行错误:

Argument of type '(e: TouchEvent) => void' is not assignable to parameter of type 'EventListener'.
  Type '(e: TouchEvent) => void' is not assignable to type 'EventListener'.
    Types of parameters 'e' and 'evt' are incompatible.
      Type 'Event' is missing the following properties from type 'TouchEvent': altKey, changedTouches, ctrlKey, metaKey, and 7 more.

事实上,我在使用document.body.addEventListener(...) 调用时得到了完全相同的结果。所以我在index.d.ts 中尝试了various definitions,但没有解决这个问题。

据我所知,我认为我需要在index.d.ts 中定义一些内容,然后在touchdownHandler 的JSDoc 中使用它。

有什么建议吗?

【问题讨论】:

    标签: javascript typescript addeventlistener touch-event jsdoc


    【解决方案1】:

    这里的问题是您需要为addEventListener 提供实际的事件类型,因此它会映射处理程序以接受这种特定类型的事件。 dom.d.ts 声明(DOM 库)包含此特定用途的事件映射。您的目标是确保 eventName 映射到处理程序的事件类型。

    虽然在 TS 中我们可以使用 param 类型,所以它可以在没有泛型的情况下完成,但是你不能用 JSDoc 做同样的事情,所以我们必须为 Event Type 引入模板变量:

    /**
     * @template {keyof HTMLElementEventMap} T
     * @param {HTMLElement} element
     * @param {T} eventName
    */
    

    还有其他事件地图,但在大多数情况下,这就足够了。如果没有 - 检查dom.d.ts 源代码。

    接下来我们需要输入处理程序:

    /**
     * @template {keyof HTMLElementEventMap} T
     * @param {HTMLElement} element
     * @param {T} eventName
     * @param {(event: HTMLElementEventMap[T]) => void} handler
     */
    

    在这种情况下,如果T 将是'click',我们将在处理程序中得到event 作为HTMLElementEventMap['click'],即MouseEvent

    最后 - options。在 JSDoc 中,您可以将参数标记为可选,而不是指定 undefined

    /**
     * @template {keyof HTMLElementEventMap} T
     * @param {HTMLElement} element
     * @param {T} eventName
     * @param {(event: HTMLElementEventMap[T]) => void} handler
     * @param {EventListenerOptions | boolean} [options]
     */
    

    这将按您的预期工作。

    完整代码:

    /**
     * @template {keyof HTMLElementEventMap} T
     * @param {HTMLElement} element
     * @param {T} eventName
     * @param {(event: HTMLElementEventMap[T]) => void} handler
     * @param {EventListenerOptions | boolean} [options]
     */
    function on(element, eventName,  handler, options) {
      const ops = options || false;
      element.addEventListener(eventName, handler, ops);
    }
    
    /**
     * test handler
     * @param {TouchEvent} e
     */
    function touchdownHandler(e) {
      console.log(e.touches)
    }
    
    // test invocation
    on(document.body, 'touchend', touchdownHandler);
    

    Sandbox

    要在其中声明,您必须使用 Generics:

    declare function on<T extends keyof HTMLElementEventMap>(element: HTMLElement, eventName: T, handler: (event: HTMLElementEventMap[T]) => void, options?: EventListenerOptions | boolean): void;
    

    【讨论】:

    • 感谢您提供答案,您能否也解释一下如何在我的index.d.ts 中编写@template 部分?再次感谢!
    • 我问这个是因为我需要on() 也用于DocumentEventMap,所以我需要定义它可能更广泛。我也有一个off() 和一个one() 变体,为每个文件一遍又一遍地定义模板是没有意义的。
    • 在打字稿中它将是genericHTMLElementEventMap 已经相当广泛,因为它的定义是`interface HTMLElementEventMap extends ElementEventMap, DocumentAndElementEventHandlersEventMap, GlobalEventHandlersEventMap {}`,但我会更新我的答案以包含它。
    • 虽然您的沙盒示例运行良好,但我认为需要有单独的定义 declare type AddorRemoveEventListener,然后是 declare type EventHandler,这样我才能以某种方式重用/集成 one() 实用程序。再次感谢。
    • 如果on(document, 'event', callback) 出现错误,这将无法正常工作。
    【解决方案2】:

    因为我已经标记了答案,所以我也将提供我的解决方案,这比我想象的要简单得多:利用 EventListenerObject 及其 handleEvent 方法:

    /**
     * Add eventListener to an `Element` | `HTMLElement` | `Document` | `Window` target.
     *
     * @param {HTMLElement | Element | Document | Window} element event.target
     * @param {string} eventName event.type
     * @param {EventListenerObject['handleEvent']} handler callback
     * @param {(EventListenerOptions | boolean)=} options other event options
     */
    function on(element, eventName, handler, options) {
      const ops = options || false;
      element.addEventListener(eventName, handler, ops);
    }
    

    现在一切正常。

    on(document, 'load', (e) => console.log(e), false)
    
    on(window, 'resize', (e) => console.log(e.type), false)
    
    /**
     * test handler
     * @type {(event: Event) => void}
     * @param {TouchEvent} e
     */
     function touchdownHandler(e) {
      console.log(e.touches)
    }
    
    const div = document.createElement('div')
    on(div, 'touchstart', touchdownHandler, false)
    
    const main = document.createElement('main')
    on(main, 'touchstart', touchdownHandler, false)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-07-21
      • 1970-01-01
      • 2017-12-17
      • 1970-01-01
      • 1970-01-01
      • 2020-03-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多