【TS】型推論が効かないEventオブジェクトへの対処法
TypeScriptで必要不可欠な機能と言えば型推論。
しかし、中には型推論が上手く機能しない事例も存在します。
例えば、タイトルにあるevent
オブジェクトなどです。
今回はこのevent
オブジェクトを用いて、
型推論が上手く機能しない原因と対処法についてご紹介します。
Eventオブジェクトとは
event
オブジェクトはJavaScriptに備わるオブジェクトで、
特定のイベントが発生した際の詳細情報を提供するオブジェクトになります。
このオブジェクトは、イベントハンドラー(イベントに対するリスナーとして登録した関数)へ自動的に渡されるという特徴を持ちます。
例えば、HTML上に配置されたボタンをマウスでクリックした時を考えてみます。
コードで示すと以下の通りです。
const handleClick = (event) => {
if (event) {
console.log(event)
}
}
const button_1 = document.getElementById('button-1')
if(button_1) {
button_1.addEventListener('click', (event) => handleClick(event))
}
上記のコードでは、button_1
に対してclick
イベントリスナーが付与されており、
イベントハンドラーとしてhandleClick関数が設定されています。
そして、handleClick関数に渡されている引数がevent
オブジェクトです。
event
オブジェクトはイベントが発生した際 (今回の場合はマウスでボタンをクリックした際),
自動的にイベントハンドラーであるhandleClick関数へ渡される事になります。
具体的にどのような情報がeventオブジェクトへ渡されるかについては、発生したイベントの種類によって異なります。
例えば、マウスによるクリックの際はMouseEventがeventオブジェクトとして渡されます。
※他のEventについても知りたい方は、下記リンク先をご参照ください。
イベント一覧(MDN Web Docs)
実際に上記コードを実行してみると、下図のようにコンソール上にてeventの中身が確認できます。
Eventオブジェクトの型について
型推論をした場合
上述したapp.jsのコードを、app.tsへと書き換えたいと思います。
この時、handleClick関数の引数として渡されるevent
にはどのような型を当てはめればよいでしょうか。
対象となるevent
をマウスオーバーして、型推論を行ってみます。
型推論の結果、event
はMouseEvent型ということが分かりました。
よって、event
の型として下記のようにMouseEventを当てはめてあげれば、
JS⇒TSへの変換が完了となります。
const handleClick = (event: MouseEvent) => {
if (event) {
console.log(event)
}
};
const button_1 = document.getElementById('button-1')
if(button_1) {
button_1.addEventListener('click', (event: MouseEvent) => handleClick(event))
};
型推論で上手くいかない場合
上記のコードを少し書き換えてみます。
クリックした際にeventの中身をコンソール上に出力するのではなく、
ボタン内に記載されている"1"を出力するとします。
先ほどのconsole.log(event)
の出力を利用して、
ボタンの"1"がMouseEvent内のどのプロパティに保持されているのかを確認します。
すると、MouseEventのtargetプロパティ内にあるinnerHTMLプロパティにて
ボタンに記載されている"1"が保持されている事が分かります。
よって、先ほどのapp.tsは以下のように書き換えられるはずです。
const handleClick = (event: MouseEvent) => {
if (event) {
+ console.log(event.target.innerHTML)
- console.log(event)
}
};
const button_1 = document.getElementById('button-1')
if(button_1) {
button_1.addEventListener('click', (event: MouseEvent) => handleClick(event))
};
しかし、event.target.innerHTMLと書き換えると、
型が勝手にEventTarget型であると解釈され、EventTarget型にはinnerHTMLプロパティは存在しないというエラーが出てきてしまいます。
Eventオブジェクトにて型推論が上手く機能しない原因
event
の型として型推論ではMouseEvent型と出ていて、MouseEvent内に実際にinnreHTMLプロパティがあるにも関わらず、上記のコードでエラーとなってしまうのはなぜでしょうか。
その答えは、TypeScriptで標準実装されている型定義ファイルにて、MouseEventの全てのプロパティが型定義されていないからとなります。
実際に、MouseEvent
が標準の型定義ファイルでどのように型定義されているかを確認してみたいと思います。
MouseEvent
の型定義内には、target
プロパティは見当たりません。
MouseEventはUIEventの型定義を継承している(MouseEvent extends UIEvent
の部分)ので、今度はUIEventを確認してみます。
UIEvent
の型定義内でもtarget
プロパティは見当たりません。
同様に継承元であるEvent
プロパティを調べてみます。
Event
の型定義内でようやくtarget
プロパティの型定義を発見しました。
どうやら、target
プロパティはEventTarget
型またはnull
型と定義されているようです。
基本的に型推論は標準実装の型定義ファイルを基に行われるので、
MouseEvent
にてtarget
を用いると、その型はEventTarget
型またはnull
型と型推論されることになります。
前節のエラーメッセージの中で突然出てきたEventTarget
ですが、EventTarget
はevent.target
が型推論されて導出されたものという事が分かります。
では、EventTarget
はどのように型定義されているのかを確認してみます。
EventTarget
の型定義内ではinnerHTML
プロパティは見当たりません。
まさに前節のエラーメッセージの通り、EventTarget
にはinnerHTML
プロパティが無いという状況です。
EventTarget
は何も継承していないので、ここまででMouseEvent
の型定義は出揃ったことになります。すなわち、MouseEvent
の型定義内には、innerHTMLプロパティが定義されていないという事が分かります。
※Eventオブジェクトにおいてプロパティが全て定義されていない理由
Eventオブジェクトの概要について説明した際にご紹介した通り、Eventオブジェクトには主にイベントが発生するまで確定しない情報が渡される事になります。
イベントが発生するタイミングや状況(マウスで入力するかキーボードで入力するか等)でプロパティが変動する可能性があるため、一部のプロパティについては標準で型定義が出来なかったのだと思われます。
対処法
上述のケースのように型推論では上手くいかない場合、
対処法の一つとして型アサーション(型の強制)を利用するというものがあります。
const handleClick = (event: MouseEvent) => {
if (event) {
+ console.log((event.target as Element).innerHTML)
- console.log(event.target.innerHTML)
}
};
const button_1 = document.getElementById('button-1')
if(button_1) {
button_1.addEventListener('click', (event: MouseEvent) => handleClick(event))
};
上記のコードでは、event.target
の型をElement
型であると指定しています。
標準の型定義ファイル(lib.dom.d.ts
)にてInnerHTMLがどのように型定義されているか調べると、InnerHTML
は個別で型定義されておりElement
の型定義へ継承されていることが分かります。
よって、event.targetの型をElement
型であると指定することにより、
エラーを回避しています。
まとめ
今回は、TypeScriptにてEventオブジェクトを扱う際の対処法についてご紹介しました。
参考文献を眺めていると、この問題については様々な方が頭を悩ませているようで、自分だけではないのだと少しホッとしました...
この記事では型アサーションによる解法しかご紹介出来ませんでしたが、他にも方法があるようで、まだまだ理解出来ていないところが沢山あるなと感じました。
少しずつ精進したいなと思います。
参考文献
TypeScript Event.target, Event.currentTarget の型がむずい!
【TypeScript】Reactでのイベントに設定する型の理解が少しだけ深まる
TypeScriptでEventの取り扱いがめんどくさ過ぎる。。。
Discussion