thumbnail

【Vue3+TypeScript】v-onイベントハンドラの引数のキャスト

Vue.js3において、v-onディレクティブで紐づけた「イベントハンドラ」や「トランジションフック」の引数から要素ノードのプロパティを参照、アクセスしようとするとTypeScriptではエラーが出てしまいます。そこで引数のダウンキャストが必要なのですが、「イベントハンドラ」と「トランジションフック」でそれぞれキャストの方法が違うので順番に説明します。

キャストの方法

先にキャストの方法を示し、順次解説します。
結論を言うと、次のコードのようになります。

//@clickや@mouseenterなどのイベントハンドラ
const onclick = (e: Event) => {
  const target = e.target as HTMLElement //もしくはHTMLElementのサブクラス
  console.log(target.id) //要素ノードにアクセス
}

//@before-enterなどのトランジションフック
const onenter = (e: Element) => {
  const elm = e as HTMLElement //もしくはHTMLElementのサブクラス
  console.log(elm.style) //要素ノードにアクセス
}

TypsScriptを用いていない、普通のJavascript+Vue.jsの文献などでは、上記のようにどちらも同じ引数名「e」として受け取っていることが多いです。しかしTypeScriptで引数の型を意識すると、それぞれ全く違う型であることが分かります。

それでは、解説を行っていきます。

エラーの原因と解説

一般的なイベントハンドラに渡される引数

clickやmouseenterなど、一般的なイベントに紐づく引数について説明します。
以下のようなケースです。

・テンプレート

<template>
  <a @click="onclick" id="hoge">~</a>
</template>

JavaScriptでは次のコードでもエラーが出ません。

const onclick = e => {
  console.log(e.target.id)//eを要素ノードと仮定
}

しかし、TypeScriptではエラーが出ます。
ここで、イベントハンドラに渡される引数の型はEvent型(もしくはそのサブクラス)になります。
JavaScriptで意図したような要素ノードを取得するには、Event型のtargetプロパティをきちんとHTMLElement型にアサーションしてアクセスします。

const onclick = (e: Event) => {
  const target = e.target as HTMLElement //もしくはHTMLElementのサブクラス
  console.log(target.id)//eを要素ノードと仮定
}

トランジションフックに渡される引数

<transition>タグにおいて、before-enterやafter-leaveに紐づくイベントハンドラ(トランジションフック)へ渡される引数は一般的なイベントハンドラとは異なります。
この場合、イベントハンドラの引数はElement型となります。具体的に言うと、<transiton>タグ直下のHTML要素がそのまま表示されます。

・テンプレート

<transition @enter="onenter">
  <h1 class="heading">fugafuga</h1>
</transition>

スクリプト(JS)

const onenter = e => {
  console.log(e)
  // <h1 class="heading">fugafuga</h1>が出力される
}

要素ノードの具体的な属性にアクセスしたい場合は、HTMLElement(もしくはそのサブクラス)にダウンキャストします。

スクリプト(TS)

const onenter = (e: Element) => {
  const elm = e as HTMLElement
  console.log(elm.style)
}

イベント周りのクラスの関係

EventやHTMLElementなどでダウンキャストが多く登場しましたが、そもそもこれらのクラスの関係は次のようになっています。

矢印の先が親クラスになっています。ですので、矢印を逆に辿ればどんどん具体的な型になっていきます。

まとめ

イベントハンドラの引数のキャストの方法について解説し、普通のイベントハンドラとトランジションフックでは異なる型が渡されていることも説明しました。