如何为 TouchEvent 处理程序定义 JSDoc

How to define JSDoc for TouchEvent handler

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

我有使用原生 Element.addEventListeneron() 代码段,到目前为止还不错, 问题是当使用 TouchEvent 作为实际处理程序的参数时,TypeScript 会抛出 4 行错误,请参见下面的代码片段。

/**
 * @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 中使用它

有什么建议吗?

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

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

/**
 * @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;

因为我已经标记了一个答案,所以我也将提供我的解决方案,这比我想象的要简单得多:利用 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)