[Vue入門] Emitの使い方

このページは、コンポーネントの基本をすでに読んでいることを前提としています。コンポーネントを初めて使用する場合は、最初にそれをお読みください。

イベントの送信とリスニング

$emitを使用してコンポーネントからv-onでバインドし、テンプレートにカスタムイベントを直接設定することができます。

<!-- MyComponent -->
<button @click="$emit('someEvent')">click me</button>

次に、親はv-onを使用して子のcomponentから発信されたイベントを聞くことができます。

<MyComponent @some-event="callback" />

修飾子は、.onceコンポーネントイベントリスナーでもサポートされています。

<MyComponent @some-event.once="callback" />

コンポーネントやPropsと同様に、イベント名はケースの自動変換を提供します。キャメルケースイベントを使用しても、親のケバブケースのリスナーを使用してそれをどちらでもVueで同じものとして扱われることに注意してください。Propsのケーシングと同様に、テンプレートではケバブケースのイベントリスナーを使用することをお勧めします。

ヒント

ネイティブDOMイベントとは異なり、コンポーネントが発行するイベントはバブルしません。直接の子コンポーネントによって実行されたイベントのみを聞くことができます。

イベント引数

イベントで特定の値を実行すると便利な場合があります。たとえば<BlogPost>、テキストをどれだけ拡大するかをコンポーネントが担当するようにしたい場合があります。$emitそのような場合、この値を提供するために追加の引数を渡すことができます。

<button @click="$emit('increaseBy', 1)">
  Increase by 1
</button>

次に、親でイベントをリッスンするときに、インライン関数をリスナーとして使用できます。これにより、イベント引数にアクセスできます。

<MyButton @increase-by="(n) => count += n" />

または、イベントハンドラーがメソッドの場合:

<MyButton @increase-by="increaseCount" />

次に、その値がそのメソッドの最初のパラメーターとして渡されます。

function increaseCount(n) {
  count.value += n
}

ヒント

イベント名の後に渡されたすべての追加の引数は$emit()、リスナーに転送されます。たとえば$emit('foo', 1, 2, 3)、listener関数では3つの引数を受け取ります。

Emitイベントの宣言

defineEmits()放出されたイベントは、マクロを介してコンポーネントで明示的に宣言できます。

<script setup>
defineEmits(['inFocus', 'submit'])
</script>

で使用した$emitメソッドは、コンポーネント<template>のセクション内ではアクセスできませんが、代わりに使用できる同等の関数を返します。<script setup>defineEmits()

<script setup>
const emit = defineEmits(['inFocus', 'submit'])

function buttonClick() {
  emit('submit')
}
</script>

defineEmits()マクロは関数内では使用できません<script setup>。上記の例のように、マクロ内に直接配置する必要があります。

setupの代わりに明示的な関数を使用している場合は、オプション<script setup>を使用してイベントを宣言する必要があります。

export default {
  emits: ['inFocus', 'submit'],
  setup(props, ctx) {
    ctx.emit('submit')
  }
}

setup()をしようした場合はコンテキストは他のプロパティと同様です。

export default {
  emits: ['inFocus', 'submit'],
  setup(props, { emit }) {
    emit('submit')
  }
}

このemitsオプションは、オブジェクト構文もサポートしています。これにより、実行されたイベントのペイロードの実行時検証を実行できます。

<script setup>
const emit = defineEmits({
  submit(payload) {
    // return `true` or `false` to indicate
    // validation pass / fail
  }
})
</script>

TypeScriptをで使用している場合は<script setup>、純粋な型アノテーションを使用して発行されたイベントを宣言することもできます。

<script setup lang="ts">
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()
</script>

詳細:コンポーネントエミットの入力 

オプションですが、コンポーネントがどのように機能するかをより適切に文書化するために、発行されたすべてのイベントを定義することをお勧めします。また、Vueが既知のリスナーをフォールスルー属性から除外することもできます。

ヒント

オプションでネイティブイベント(たとえばclick)が定義されているemits場合、リスナーはコンポーネントから送信されたイベントのみをリッスンし、ネイティブイベントに応答しなくなります。

イベントの検証

プロップタイプの検証と同様に、発行されたイベントは、配列構文ではなくオブジェクト構文で定義されている場合に検証できます。

検証を追加するために、イベントには、呼び出しに渡された引数を受け取り、イベントが有効かどうかを示すブール値を返す関数が割り当てられます。emit

<script setup>
const emit = defineEmits({
  // No validation
  click: null,

  // Validate submit event
  submit: ({ email, password }) => {
    if (email && password) {
      return true
    } else {
      console.warn('Invalid submit event payload!')
      return false
    }
  }
})

function submitForm(email, password) {
  emit('submit', { email, password })
}
</script>

v-modelとEmitの使い方

カスタムイベントを使用して、v-modelと連携できるカスタムインプットを作ることもできます。

<input v-model="searchText" />

上記のコードは下のものと同じことを意味します。

<input
  :value="searchText"
  @input="searchText = $event.target.value"
/>

コンポーネントで使用する場合、少し違う動きが発生し、下記のようになります。

<CustomInput
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>

ただし、これを実際に機能させるには、<input>のコンポーネントの内部で次のことを行う必要があります。

  • value属性をmodelValueプロップにバインドします
  • で、新しい値でイベントを発行inputしますupdate:modelValue

これが実際の動作です。

<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

これv-modelで、このコンポーネントで完全に機能するはずです。

<CustomInput v-model="searchText" />

遊び場で試してみてください

v-modelこのコンポーネント内に実装する別の方法はcomputed、ゲッターとセッターの両方で書き込み可能なプロパティを使用することです。メソッドはプロパティをget返し、メソッドは対応するイベントを発行する必要があります。modelValueset

<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const value = computed({
  get() {
    return props.modelValue
  },
  set(value) {
    emit('update:modelValue', value)
  }
})
</script>

<template>
  <input v-model="value" />
</template>

v-model引数

デフォルトでv-modelは、コンポーネントmodelValueで小道具およびupdate:modelValueイベントとして使用されます。引数を渡してこれらの名前を変更できますv-model

<MyComponent v-model:title="bookTitle" />

この場合、子コンポーネントはtitle小道具を期待しupdate:title、親の値を更新するイベントを発行する必要があります。

<!-- MyComponent.vue -->
<script setup>
defineProps(['title'])
defineEmits(['update:title'])
</script>

<template>
  <input
    type="text"
    :value="title"
    @input="$emit('update:title', $event.target.value)"
  />
</template>

遊び場で試してみてください

複数v-modelのバインディング

v-model以前に引数で学習した特定の小道具とイベントをターゲットにする機能を活用することで、単一のコンポーネントインスタンスに複数のvモデルバインディングを作成できるようになりました。

各v-modelは、コンポーネントに追加のオプションを必要とせずに、異なるプロップに同期します。

<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>
<script setup>
defineProps({
  firstName: String,
  lastName: String
})

defineEmits(['update:firstName', 'update:lastName'])
</script>

<template>
  <input
    type="text"
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  />
  <input
    type="text"
    :value="lastName"
    @input="$emit('update:lastName', $event.target.value)"
  />
</template>

遊び場で試してみてください

v-model修飾子(モディファイヤ)

フォーム入力バインディングについて学習していたときに、.trim.number.lazyのような修飾子v-modelが組み込まれていることを確認しました。ただし、場合によっては、独自のカスタム修飾子を追加することもできます。

バインディングcapitalizeによって提供される文字列の最初の文字を大文字にするカスタム修飾子の例を作成しましょう。v-model

<MyComponent v-model.capitalize="myText" />

コンポーネントに追加されたモディファイヤは、プロップv-modelを介してコンポーネントに提供されます。次の例では、デフォルトで空のオブジェクトにmodelModifiersなるpropを含むコンポーネントを作成しました。

<script setup>
const props = defineProps({
  modelValue: String,
  modelModifiers: { default: () => ({}) }
})

defineEmits(['update:modelValue'])

console.log(props.modelModifiers) // { capitalize: true }
</script>

<template>
  <input
    type="text"
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

コンポーネントのmodelModifierspropにが含まれていてcapitalize、その値がtrueであることに注意してください。これは、v-modelバインディングに設定されているためv-model.capitalize="myText"です。

プロップが設定されたので、modelModifiersオブジェクトキーを確認し、出力された値を変更するハンドラーを作成できます。<input />以下のコードでは、要素がイベントを発生させるたびに文字列を大文字にしますinput

<script setup>
const props = defineProps({
  modelValue: String,
  modelModifiers: { default: () => ({}) }
})

const emit = defineEmits(['update:modelValue'])

function emitValue(e) {
  let value = e.target.value
  if (props.modelModifiers.capitalize) {
    value = value.charAt(0).toUpperCase() + value.slice(1)
  }
  emit('update:modelValue', value)
}
</script>

<template>
  <input type="text" :value="modelValue" @input="emitValue" />
</template>

遊び場で試してみてください

v-model引数と修飾子の両方を持つバインディングの場合、生成されるプロップ名はarg + "Modifiers"になります。例えば:

<MyComponent v-model:title.capitalize="myText">

対応する宣言は次のようになります。

const props = defineProps(['title', 'titleModifiers'])
defineEmits(['update:title'])

console.log(props.titleModifiers) // { capitalize: true }