😸

Element.addEventListener で Event 以外を指定したときのTypeScript のエラー対応

2021/10/07に公開

事象

TypeScript で Element 要素に対して、addEventListener でイベントを指定する。
その際に第2引数の匿名関数の引数 event の型を Event ではなく、 TouchEvent にするとコンパイルエラーが発生した。

const container = document.getElementsByClassName("container")[0];
container.addEventListener("touchStart", (event: TouchEvent) => {
    ...
});

エラーメッセージ

TS2769: No overload matches this call.   
  Overload 1 of 2, '(type: "fullscreenchange" | "fullscreenerror", listener: (this: 
Element, ev: Event) => any, options?: boolean | AddEventListenerOptions | undefined): void', gave the following error.
    Argument of type 'string' is not assignable to parameter of type '"fullscreenchange" | "fullscreenerror"'.
  Overload 2 of 2, '(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | undefined): void', gave the following error.
    Argument of type '(event: TouchEvent) => void' is not assignable to parameter of type 'EventListenerOrEventListenerObject'.
      Type '(event: TouchEvent) => void' is not assignable to type 'EventListener'. 
        Types of parameters 'event' and 'evt' are incompatible.
          Type 'Event' is missing the following properties from type 'TouchEvent': altKey, changedTouches, ctrlKey, metaKey, and 6 more.

原因

TypeScript の strictFunctionTypes の設定によるもの。(TypeScript 2.6 で追加)

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-6.html#strict-function-types

対策

対策1 tsconfig.json の設定変更

tsconfig.jsonで

"strictFunctionTypes": false

と設定して、 String Function Types を無効にする。

対策2 アサーション <TouchEvent> の利用

引数は Event として、関数内部で <TouchEvent>event で置き換える。

const container = document.getElementsByClassName("container")[0];
container.addEventListener("touchStart", (event: Event) => {
    const touchEvent = <TouchEvent>event;
    ...
});

対策3 アサーション as EventListener の利用

引数は TouchEvent として、関数にアサーション as EventListener を指定する。

const container = document.getElementsByClassName("container")[0];
container.addEventListener("touchStart", ((event: TouchEvent) => {
    ...
}) as EventListener);

対策4 is を利用する。

TouchEventであることをチェックする関数 isTouchEvent を作成する。

引数は Event として、関数内部で isTouchEvent を呼び出す。
その後、 event は 型 TouchEvent に絞り込まれる。

function isTouchEvent(event: Event): event is TouchEvent {
  return 'touches' in event;
}

const container = document.getElementsByClassName("container")[0];
container.addEventListener("touchStart", (event: TouchEvent) => {
  if (!isTouchEvent(event)) {
    throw new Error('not a touch event');
  }
  ...
});

参考サイト

https://stackoverflow.com/questions/47166369/argument-of-type-e-customevent-void-is-not-assignable-to-parameter-of-ty/47171216#47171216

https://github.com/microsoft/TypeScript/issues/28357

https://qiita.com/tobita0000/items/7341e11305eb25726dc0

Discussion