🚁

definePropsとdefineEmitsおさらい

2022/10/12に公開

紹介できること

Vue3で利用できるdefinePropsとdefineEmitsについておさらいします!

  • props、emits親と子の具体的な書き方
  • defineProps、インポートしてきた型使えない

definePropsのつかいかた

今回は props.title を受け取って<h1>で囲むだけの単純なコンポーネントを例に取ります。

子コンポーネント(共通コンポーネントなどの、ページから使われるようなコンポーネント)

components/common/Title.vue
<script setup lang="ts">
const props = defineProps<{
  title: string
}>()
</script>

<template>
  <h1>{{ title }}</h1>
</template>

親コンポーネント(ページなどの、使う側のコンポーネント)

pages/index.vue
<template>
  <common-title title="definePropsとdefineEmits">
</template>

さらに注意点

外部ファイルからインポートしてきた型をdefinePropsの引数に入れられないことに注意しましょう。

現在のところ、正しく静的解析を行うためには、型宣言の引数は以下のいずれかでなければなりません:

  • 型リテラル
  • 同一ファイル内のインタフェースか型リテラルへの参照

現在、複雑な型や他のファイルからの型のインポートはサポートされていません。将来的には型のインポートをサポートすることも理論的には可能です。

https://v3.ja.vuejs.org/api/sfc-script-setup.html#typescript-のみの機能

UIコンポーネントのVuetify、ionicが提供する型や amplify codegen で自動生成されるAPI.tsなどをそのままdefinePropsに渡したいところですが、現時点(2022/10/12)のVue3.2ではサポートされていません。
以下の実装はNG例です。

import type { IonInput } from '@ionic/core/components'
const props = defineProps<IonInput>()
[plugin:vite:vue] [@vue/compiler-sfc] type argument passed to defineProps() must be a literal type, or a reference to an interface or literal type

このエラーの突破口はなく、Vue3.3のリリースを待つしかないようです…

Just for the record, Evan You told it would be addressed in Vue 3.3.
The video states they would start working on it after the 2.7 stable release which happened on July 1, 2022.
Vue 3.3 ETA seems to be Q3 2022, let us be patient, friends 😃

https://github.com/vuejs/core/issues/4294#issuecomment-1204534444
↑ハックしたり、ハックできなかったりと皆さんの試行錯誤が面白いので時間があれば見てみてください

defineEmitsのつかいかた

inputへの入力値を受け取るonChangeイベントを例に取ります。

子コンポーネント

components/common/field/Text.vue
<script setup lang="ts">
const emits = defineEmits<{(e: 'change', value?: string): void}>()
const onChange = (value?: string): void => {
  emits('change', value)
}
const inputValue = ref<string>('')
</script>

<template>
  <input
    v-model="inputValue"
    @change="onChange(inputValue)"
  />
</template>

e: にemitイベント名、value?: に任意で親に渡したい引数を渡すことが出来ます。
今回は、change というemitイベント名に入力値である inputValue を引数として指定しています。
templateのinputタグから実行されるonChange関数内でemitsを呼ぶ形を取ります。

親コンポーネント

pages/index.vue
<script setup lang="ts">
const onChange = (value?: string) => {
  console.log(value)
}
</script>

<template>
  <common-field-text @change="onChange" />
</template>

<common-field-text> 内の @changecommon/field/Text.vue で指定したemitイベント名)に使いたい関数( "onChange" )を登録するだけで、inputValueが親でも利用できるようになるわけです。

おわりに

外部ファイルの型をインポートしてきて使えないのはつらいですね…
対応が期待されるVue3.3は2022年10月から12月を目処にリリース予定らしいので、それまで(手動で書きながら)待ちましょう。

参考

https://tekrog.com/vue3-script-setup/
https://zenn.dev/azukiazusa/articles/676d88675e4e74

Discussion