🎩

Vue Component の型に困ったらとりあえず InstanceType を使おう

2022/11/07に公開

Vue Component の型は InstanceType で取れる

Vue TypeScript で開発していると、単一ファイルで書かれた Vue Component の型が欲しくなることが時々あります。

Template上の props や、同一ファイル内でメソッドを呼ぶ場合なんかの型の自動推論はここ数年で大きく改善されてきており、Composition API、Volar、そして適切なライブラリを使っていれば困ることは少なくなってきました。

一方、やはり純粋なTypeScriptではないため、Vue ライフサイクルの外で .vue ファイルを import した際に、どういう型として読み込まれているのかわかりづらく、ちょっと複雑な型パズルをしたくなった時に悩んでしまうかもしれません。

そんな時に役立つのが InstanceType です。 これがあれば上述のような悩みの大半は解消します。

※以下、コード量の少ない <script setup> 構文を使っていますがそれ以外の記法や Vue 2 でも同じように扱えます。

子コンポーネントの Props の型を取りたい

例えば、親コンポーネントで子コンポーネントに渡す props を、script タグ内でまとめて1つのオブジェクトにしたい場合。

<script lang="ts" setup>
import ChildComponent from '@/components/ChildComponent.vue'

const childProps = { id: 1, name: '', email: '' }
</script>

<template>
  <ChildComponent :="childProps" />
</template>

v-bind="childProps" で複数の props をまとめて1つのオブジェクトとして渡しています。

この書き方でも、ChildComponent の props と結果的に一致しなければtemplateタグ内で型エラーになりますが、できれば scriptタグの時点でエラーにしてほしい。

そんなときに役立つのが InstanceType です。

<script lang="ts" setup>
import ChildComponent from '@/components/ChildComponent.vue'

const childProps: InstanceType<typeof ChildComponent>['$props'] = {}
</script>

<template>
  <ChildComponent :="childProps" />
</template>

このように ['$props'] で props の型のみを抽出できるので、TS内で子コンポーネントの props の型を参照できます。

$props 以外にも、 $data とか $el とか $children とか、 Options API の this に生えてたものはだいたい取れます。

テンプレート参照したコンポーネントのメソッドを呼びたい

コンポーネントの methods(または setup() で return している関数)を外部から呼び出して実行したい場合。ちょっとお行儀が悪いですが、必要になることはあるでしょう。

これも InstanceType で解決できます。

<script lang="ts" setup>
import ChildComponent from '@/components/ChildComponent.vue'

const ChildComponentRef = ref<InstanceType<typeof ChildComponent>>()

onMounted(() => {
  ChildComponentRef.value?.submit()
})
</script>

<template>
  <ChildComponent ref="ChildComponentRef" />
</template>

マウント完了する前は null の可能性があるので ?. を付けています。

外部ライブラリのUIコンポーネントのメソッドもこの方法で呼べます。バリデーションコンポーネントのリセットとかで使えると思います。

詳しくは以下のページの「コンポーネントのテンプレート参照の型付け」という項目に載っています。

https://ja.vuejs.org/guide/typescript/composition-api.html#コンポーネントのテンプレート参照の型付け

ちなみに <script setup> 記法の場合、子コンポーネントでは defineExpose() で外部参照できる関数を明示的に指定してください。

https://ja.vuejs.org/api/sfc-script-setup.html#defineexpose

ユニットテストで shallowMount する

@vue/test-utilsmountshallowMount で wrapper を作って、そのメソッドを呼び出したい場合。

const wrapper = shallowMount(ChildComponent) as Wrapper<InstanceType<typeof ChildComponent>>

as を使っているのがややイケてない気がします(もっと良い書き方をご存知の方はぜひ教えてください!)。

が、これで wrapper.vm.$data のように data を読み書きしたり、wrapper.vm.hoge で methods を呼び出したりが全てタイプエラーにならずに行えます。

Composition API であれば setup 関数で return しているものは全て wrapper.vm.xxx でアクセスできます。

また、<script setup> でも、Vue Test Util であれば全ての関数・変数が自動で expose されているのでアクセス可能です。

https://github.com/vuejs/test-utils/pull/931

まとめ

この InstanceType を使った型付けの方法、探せば普通にドキュメントにも書いてあるのですが、自分でもよく忘れてしまうのと、こういうタイトルで書かれた記事が意外となかったので書いてみました。

Vue SFC の型をどうすれば良いかわからない、という問題の半分くらいは InstanceType で解決できますし、逆に InstanceType の存在を知らないと Vue TypeScript での記述がかなり制限されたものになってしまいます。

この記事をきっかけに、InstanceType<typeof Component> という書き方を、頭の片隅に置いてもらえれば幸いです。

Discussion