[Vue入門] フォールスルー属性

このページは、コンポーネントの基礎をすでに読んでいることを想定して説明しています。初めてコンポーネントに触れる方は、まずそちらをお読みください。

属性の継承

“フォールスルー属性”とは、あるコンポーネントに渡されたものの、受け取ったコンポーネントの props や emits で明確に宣言されていない属性、または v-on イベントリスナーを指します。よくある例としては、classstyleid 属性などがあります。

コンポーネントが単一のルート要素をレンダリングする時、フォールスルー属性は自動的にルート要素の属性に追加されます。例えば、次のようなテンプレートを持つ <MyButton> コンポーネントがあったとします:

<!-- <MyButton> のテンプレート -->
<button>click me</button>

そして、このコンポーネントを使う親が以下です:

<MyButton class="large" />

最終的に DOM は以下のようにレンダリングされます:

<button class="large">click me</button>

class と style のマージ

もし、子コンポーネントのルート要素にすでに class や style 属性がある場合は、親から継承された class や style の値にマージされます。先ほどの例の <MyButton> のテンプレートを次のように変更するとします:

<!-- <MyButton> の テンプレート -->
<button class="btn">click me</button>

そうすると、最終的にレンダリングされる DOM は、こうなります:

<button class="btn large">click me</button>

v-on リスナーの継承

同じルールが v-on イベントリスナーにも適用されます:

<MyButton @click="onClick" />

click リスナーは <MyButton> のルート要素、つまりネイティブの <button> 要素に追加されます。ネイティブの <button> がクリックされた時、親コンポーネントの onClick メソッドがトリガーされます。もし、ネイティブの <button> が既に v-on でバインドされた click リスナーを持っている場合、両方のリスナーがトリガーされます。

ネストされたコンポーネントの継承

あるコンポーネントが他の 1 つのコンポーネントをルートノードとしてレンダリングする場合を考えてみましょう。例として、<MyButton> をルートとして <BaseButton> をレンダリングするようにリファクタリングしました:

<!-- シンプルに他の 1 つのコンポーネントをレンダリングする <MyButton/> のテンプレート -->
<BaseButton />

この時、<MyButton> が受け取ったフォールスルー属性は、自動的に <BaseButton> に転送されます。

以下の点に注意してください:

  1. 転送された属性には、<MyButton> が props として宣言した属性や、宣言したイベントの v-on リスナーは含まれません。言い換えると、宣言した props とリスナーは <MyButton> によって “消費” されています。
  2. 転送された属性は、 <BaseButton> が宣言していれば、 props として受け取ることができます。

属性の継承の無効化

コンポーネントに自動的な属性の継承をさせたくない場合は、コンポーネントのオプションで inheritAttrs: false を設定することができます。

<script setup> を使用するなら、このオプションは別の通常の <script> ブロックを使って宣言する必要があります:

<script>
// 通常の <script> でオプションを宣言
export default {
  inheritAttrs: false
}
</script>

<script setup>
// ロジックのセットアップ
</script>

属性の継承を無効にする一般的なシナリオは、ルートノード以外の要素に属性を適用する必要がある場合です。 inheritAttrs オプションを false に設定することで、フォールスルー属性を適用する場所を完全に制御することができます。

これらのフォールスルー属性は、テンプレート式で $attrs として直接アクセスすることができます:

<span>Fallthrough attributes: {{ $attrs }}</span>

$attrs オブジェクトには、コンポーネントの props や emits オプションで宣言されていないすべての属性 (例えば classstylev-on リスナーなど) が含まれます。

備考:

  • props とは異なり、フォールスルー属性は JavaScript では元のケーシングを保持します。したがって、 foo-bar のような属性は $attrs['foo-bar'] としてアクセスされる必要があります。
  • @click のような v-on イベントリスナーは、オブジェクトで $attrs.onClick という関数として公開されます。

前のセクションで紹介した <MyButton> コンポーネントの例では、スタイリングのために実際の <button> 要素を <div> でラップする必要がある場合があります:

<div class="btn-wrapper">
  <button class="btn">click me</button>
</div>

class や v-on リスナーなどのすべてのフォールスルー属性を、外側の <div> ではなく、内側の <button> に適用されるようにしたいです。これは、 inheritAttrs: false と v-bind="$attrs" で実現できます:

<div class="btn-wrapper">
  <button class="btn" v-bind="$attrs">click me</button>
</div>

引数なしの v-bind はオブジェクトのすべてのプロパティをターゲット要素の属性としてバインドすることを覚えておきましょう。

複数のルートノードでの属性継承

ルートノードが 1 つのコンポーネントと異なり、複数のルートノードを持つコンポーネントは、自動的に属性をフォールスルーするふるまいがありません。 $attrs が明示的にバインドされていない場合は、実行時に警告が出ます。

<CustomLayout id="custom-layout" @click="changeValue" />

もし <CustomLayout> が以下のようなマルチルートのテンプレートを持っている場合、 Vue はどこにフォールスルー属性を適用すればよいか分からないため、警告されます:

<header>...</header>
<main>...</main>
<footer>...</footer>

警告は $attrs が明示的にバインドされている場合は抑制されます:

<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>

JavaScript 内でフォールスルー属性にアクセスする

必要であれば、<script setup> 内で useAttrs() API を使用してコンポーネントのフォールスルー属性にアクセスすることができます:

<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()
</script>

もし <script setup> を使用していない場合、 attrs は setup() コンテキストのプロパティとして公開されます:

export default {
  setup(props, ctx) {
    // フォールスルー属性が ctx.attrs として公開される
    console.log(ctx.attrs)
  }
}

ここで attrs オブジェクトは常に最新のフォールスルー属性を反映していますが、リアクティブではないことに注意してください(パフォーマンス上の理由です)。ウォッチャーを使ってその変更を監視することはできません。リアクティビティが必要であれば、 prop を使ってください。または、 onUpdated() を使用して、更新されるたびに最新の attrs による副作用を実行することもできます。

[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 }

[Vue入門] PropsとdefineProps 

このページは、すでにコンポーネントの基礎を読んでいることを前提にしています。初めてコンポーネントに触れる方は、まずこちらをお読みください。

プロパティの宣言

Vue のコンポーネントでは、明示的な props (プロパティ) の宣言が必要です。これにより Vue は、外部からコンポーネントに渡された props を fallthrough 属性 (次のセクションで説明します) として扱うべきかを知ることができます。

プロパティは、以下のように props オプションを使って宣言します:

<script setup>
const props = defineProps(['foo'])

console.log(props.foo)
</script>

//<script setup>を使わないやり方↓
export default {
  props: ['foo'],
  created() {
    // props は `this` 上で公開されます。
    console.log(this.foo)
  }
}

プロパティの宣言には、文字列の配列に加え、オブジェクト構文を用いることもできます:

// in <script setup>
defineProps({
  title: String,
  likes: Number
})
export default {
  props: {
    title: String,
    likes: Number
  }
}

オブジェクト宣言の構文に含める各プロパティについて、キーにはプロパティの名前、値には目的の型のコンストラクター関数を指定します。

これは自分のコンポーネントを文書化するのに役立ちます。また、誤った型を渡した時にブラウザーのコンソールに警告が表示されるようになり、コンポーネントを利用する他の開発者のためにもなります。このページの後半では、プロパティのバリデーションについて詳しく説明します。

コンポーネントのプロパティの型付けも合わせて参照してください。

プロパティ渡しの詳細

プロパティ名での大文字・小文字の使い分け

長いプロパティ名は、camelCase (キャメルケース) で宣言します。そうすると、プロパティのキーとして使うときに引用符を使わなくて済みます。camelCase は JavaScript の有効な識別子であるため、以下のようにテンプレート式で直接参照することができます:

defineProps({
  greetingMessage: String
})

//<script setup>を使わないやり方↓
export default {
  props: {
    greetingMessage: String
  }
}
<span>{{ greetingMessage }}</span>

技術的には、子コンポーネントにプロパティを渡すときにも camelCase を用いることができます (ただし DOM テンプレート内を除く)。しかし、常に kebab-case (ケバブケース) を用いて HTML の属性に揃える、以下のような表記が慣例となっています:

<MyComponent greeting-message="hello" />

コンポーネントのタグには、可能な限り PascalCase を用いることが推奨されます。これは Vue コンポーネントとネイティブ要素の区別が付き、テンプレートの可読性が高まるためです。しかし、プロパティを渡すときに camelCase を用いることには、それほど実用的なメリットがありません。そのため、Vue では各言語の規約に従うことが推奨されます。

静的なプロパティと動的なプロパティ

ここまでで、静的な値として渡すプロパティを見てきました。例:

<BlogPost title="My journey with Vue" />

v-bind またはそのショートカットである : を使って、プロパティを動的に割り当てる例も見てきました。例:

<!-- 変数の値を動的に代入 -->
<BlogPost :title="post.title" />

<!-- 複雑な式の値を動的に代入 -->
<BlogPost :title="post.title + ' by ' + post.author.name" />

いろいろな種類の値を渡す

上の 2 つは、たまたま文字列の値を渡す例ですが、プロパティには どんな 種類の値も渡すことができます。

数値

<!-- `42` は静的な値ですが、これが文字列ではなく JavaScript の        -->
<!-- 式であることを Vue に伝えるため、v-bind を用いる必要があります。 -->
<BlogPost :likes="42" />

<!-- 変数の値に動的に代入します。 -->
<BlogPost :likes="post.likes" />

ブール値

<!-- 値なしでプロパティを指定すると、暗黙で `true` を指定したことになります。 -->
<BlogPost is-published />

<!-- `false` は静的な値ですが、これが文字列ではなく JavaScript の     -->
<!-- 式であることを Vue に伝えるため、v-bind を用いる必要があります。 -->
<BlogPost :is-published="false" />

<!-- 変数の値に動的に代入します。 -->
<BlogPost :is-published="post.isPublished" />

配列

<!-- 静的な配列でも、これが文字列ではなく JavaScript の         -->
<!-- 式であることを Vue に伝えるため、v-bind を用いる必要があります。 -->
<BlogPost :comment-ids="[234, 266, 273]" />

<!-- 変数の値に動的に代入します。 -->
<BlogPost :comment-ids="post.commentIds" />

オブジェクト

<!-- 静的なオブジェクトでも、これが文字列ではなく JavaScript の -->
<!-- 式であることを Vue に伝えるため、v-bind を用いる必要があります。 -->
<BlogPost
  :author="{
    name: 'Veronica',
    company: 'Veridian Dynamics'
  }"
 />

<!-- 変数の値に動的に代入します。 -->
<BlogPost :author="post.author" />

オブジェクトを利用した複数のプロパティのバインディング

オブジェクトに含まれるすべてのプロパティを props として渡したい場合には、引数なしの v-bind を使用します (:プロパティ名 の代わりに v-bind)。例えば、以下のような post オブジェクトがあるとします:

const post = {
  id: 1,
  title: 'My Journey with Vue'
}

//<script setup>を使わないやり方↓
export default {
  data() {
    return {
      post: {
        id: 1,
        title: 'My Journey with Vue'
      }
    }
  }
}

このとき、テンプレートでは以下を用いることができます:

<BlogPost v-bind="post" />

これは以下の表記と等価です:

<BlogPost :id="post.id" :title="post.title" />

一方向のデータフロー

すべてのプロパティでは、子のプロパティと親のプロパティとの間に一方向バインディングが形成されます。親のプロパティが更新されたときには子にも流れますが、その逆はありません。これにより、親の状態が誤って子コンポーネントによって変更されてアプリのデータフローが把握しにくくなる、といった事態が防がれます。

さらに、親コンポーネントが更新されるたびに、子コンポーネント内のすべてのプロパティは最新の値に更新されます。そのため、子コンポーネント内でプロパティの変更を試みてはいけません。もし試みると、Vue がコンソールで警告を発します:

const props = defineProps(['foo'])

// ❌ warning, props are readonly!
props.foo = 'bar'


//<script setup>を使わないやり方↓
export default {
  props: ['foo'],
  created() {
    // ❌ 警告、プロパティは読み取り専用です!
    this.foo = 'bar'
  }
}

通常、プロパティを変更したい状況には以下の 2 つがあります:

  • プロパティは初期値を渡すために用いて、それ以降、子コンポーネントではローカルのデータプロパティとして利用したい。 この場合、以下のようにローカルのデータプロパティを定義して、その初期値にプロパティを使用するのが最も適切です:export default { props: ['initialCounter'], data() { return { // this.initialCounter は counter の初期値を指定するためだけに // 使われ、今後発生するプロパティの更新からは切り離されます。 counter: this.initialCounter } } }
const props = defineProps(['initialCounter'])

// counter only uses props.initialCounter as the initial value;
// it is disconnected from future prop updates.
const counter = ref(props.initialCounter)
  • プロパティを、変換が必要なそのままの値として渡したい。 この場合、以下のような算出プロパティを定義して、その中でプロパティの値を利用するのが最も適切です:export default { props: ['size'], computed: { // プロパティが変更されると自動的に更新される算出プロパティ normalizedSize() { return this.size.trim().toLowerCase() } } }
const props = defineProps(['size'])

// computed property that auto-updates when the prop changes
const normalizedSize = computed(() => props.size.trim().toLowerCase())

オブジェクト/配列のプロップを変更する

オブジェクトや配列をプロパティとして渡した場合、子コンポーネントがプロパティのバインディングを変更することはできませんが、オブジェクトや配列のネストされたプロパティを変更することは可能です。これは、JavaScript ではオブジェクトや配列が参照渡しであり、Vue がそのような変更を防ぐのにかかるコストが現実的でないためです。

このような変更の主な欠点は、親コンポーネントにとって明瞭でない方法で子コンポーネントが親の状態に影響を与えることを許してしまい、後からデータの流れを見極めるのが難しくなる可能性があることです。親と子を密に結合させる設計でない限り、ベストプラクティスとしてはそのような変更を避けるべきです。ほとんどの場合、子コンポーネントはイベントを発行して、変更を親コンポーネントに実行してもらう必要があります。

プロパティのバリデーション

先ほど見た型のように、コンポーネントではプロパティに対する要件を指定することができます。要件が合わないと、Vue がブラウザーの JavaScript コンソールで警告を発します。他の人に使ってもらうことを想定したコンポーネントを開発する場合、これはとても便利です。

プロパティのバリデーションを指定するには、文字列の配列の代わりに props オプションを用いて、バリデーションの要件を持たせたオブジェクトを指定します。例:

defineProps({
  // Basic type check
  //  (`null` and `undefined` values will allow any type)
  propA: Number,
  // Multiple possible types
  propB: [String, Number],
  // Required string
  propC: {
    type: String,
    required: true
  },
  // Number with a default value
  propD: {
    type: Number,
    default: 100
  },
  // Object with a default value
  propE: {
    type: Object,
    // Object or array defaults must be returned from
    // a factory function. The function receives the raw
    // props received by the component as the argument.
    default(rawProps) {
      return { message: 'hello' }
    }
  },
  // Custom validator function
  propF: {
    validator(value) {
      // The value must match one of these strings
      return ['success', 'warning', 'danger'].includes(value)
    }
  },
  // Function with a default value
  propG: {
    type: Function,
    // Unlike object or array default, this is not a factory function - this is a function to serve as a default value
    default() {
      return 'Default function'
    }
  }
})


//<script setup>を使わないやり方↓
export default {
  props: {
    // 基本的な型チェック
    // (`null` 値と `undefined` 値は、任意の型を許可します)
    propA: Number,
    // 複数の型の可能性
    propB: [String, Number],
    // 必須の文字列
    propC: {
      type: String,
      required: true
    },
    // デフォルト値を持つ数値
    propD: {
      type: Number,
      default: 100
    },
    // デフォルト値を持つオブジェクト
    propE: {
      type: Object,
      // オブジェクトと配列のデフォルトは、ファクトリー関数を使って
      // 返す必要があります。ファクトリー関数は、コンポーネントが
      // 受け取った生の各プロパティを引数として受け取ります。
      default(rawProps) {
        return { message: 'hello' }
      }
    },
    // カスタムのバリデーター関数
    propF: {
      validator(value) {
        // 値が以下の文字列のいずれかに一致する必要がある
        return ['success', 'warning', 'danger'].includes(value)
      }
    },
    // デフォルト値を持つ関数
    propG: {
      type: Function,
      // オブジェクトや配列のデフォルトと異なり、これはファクトリー関数ではなく、デフォルト値として機能する関数です
      default() {
        return 'Default function'
      }
    }
  }
}

その他の詳細:

  • required: true が指定されていないすべてのプロパティは、デフォルトでオプションです。
  • Boolean 以外のオプションのプロパティは、値が指定されないと undefined 値になります。
  • Boolean のプロパティは、値が指定されないと false に変換されます。望みの動作を得るためには、default の値を指定する必要があります。
  • default の値を指定すると、プロパティの値が undefined に解決される時、それが使用されます。プロパティが指定されなかった場合と、明示的に undefined 値が渡された場合も、これに含まれます。

プロパティのバリデーションに失敗すると、Vue がコンソールに警告を出します (開発ビルドを使用する場合)。

注意

プロパティのバリデーションは、コンポーネントのインスタンスが生成されるに実行されます。そのため、default や validator 関数の中ではインスタンスのプロパティ (例えば datacomputed など) が使用できないことに注意してください。

実行時の型チェック

type には、以下のネイティブコンストラクターを指定することができます:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

加えて、type にはカスタムのクラスやコンストラクター関数を指定することもできます。その場合、instanceof チェックによってアサーションが行われます。例えば、次のクラスがあったとします:

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
}

これをプロパティの型として用いるとします:

defineProps({
  author: Person
})

//<script setup>を使わないやり方↓
export default {
  props: {
    author: Person
  }
}

こうすると、author というプロパティの値が new Person で作成されたものであることをバリデーションで確かめることができます。

ブール値の型変換

Boolean 型のプロパティは、ネイティブのブール値の属性が振る舞う様子を模倣するために、特殊な型変換の規則を持っています。次のような宣言を含む <MyComponent> があるとします:

export default {
  props: {
    disabled: Boolean
  }
}

このコンポーネントは、次のように使用することができます:

<!-- :disabled="true" を渡すのと同等 -->
<MyComponent disabled />

<!-- :disabled="false" を渡すのと同等 -->
<MyComponent />

また、以下のように複数の型を受け付けるようにプロパティを宣言した場合:

export default {
  props: {
    disabled: [Boolean, Number]
  }
}

Boolean の型変換の規則は、型が登場する順序に関係なく適用されます。

[Vue入門]コンポーネントの登録

このページは、すでにコンポーネントの基礎を読んでいることを前提にしています。初めてコンポーネント学ぶ方は、まずそちらをお読みください。

Vue のコンポーネントをテンプレートで使用する時は、それがどこで実装されているかを Vue に知らせるため、「登録」を行う必要があります。コンポーネントの登録方法には、グローバルとローカルの 2 つがあります。

グローバル登録

開発中の Vue アプリケーションでグローバルにコンポーネントを利用できるようにするには、以下に示す app.component() メソッドを使用します:

import { createApp } from 'vue'

const app = createApp({})

app.component(
  // the registered name
  'MyComponent',
  // the implementation
  {
    /* ... */
  }
)

SFC を使用する場合は、インポートした .vue ファイルを登録します:

import MyComponent from './App.vue'

app.component('MyComponent', MyComponent)

app.component() メソッドはチェーンにすることができます:

app
  .component('ComponentA', ComponentA)
  .component('ComponentB', ComponentB)
  .component('ComponentC', ComponentC)

グローバル登録したコンポーネントは、アプリケーション内の任意のコンポーネントのテンプレートで使用することができます:

<!-- これはアプリ内のどのコンポーネントでも動作します -->
<ComponentA/>
<ComponentB/>
<ComponentC/>

これは、サブコンポーネントにも漏れなく適用されます。そのため、上の 3 つのコンポーネントはすべて 各コンポーネント内でも互いに 使える、ということになります。

ローカル登録

グローバル登録は便利な反面、以下に示すいくつかの欠点があります:

  1. グローバル登録では、未使用のコンポーネントを削除してくれるビルドシステムの処理 (いわゆる「ツリーシェイク」) が阻害されます。グローバル登録したコンポーネントは、最後までアプリのどこにも用いなかった場合でも、最終的なバンドルには含まれてしまいます。
  2. グローバル登録では、大規模なアプリケーションでの依存関係の分かりやすさが低下します。グローバル登録では、子コンポーネントを使っている親コンポーネントから、子コンポーネントの実装部分を探し出すことが難しくなります。きわめて多くのグローバル変数が使われている状況と同じように、これは長期的な保守性に影響を与える可能性があります。

ローカルでの登録を利用すると、登録したコンポーネントを使えるスコープが現在のコンポーネントのみに限定されます。これによって依存関係が分かりやすくなり、ツリーシェイクが働きやすくなります。

ローカル登録は、以下のように components オプションを使って行います:

でSFCを使用する場合<script setup>、インポートされたコンポーネントは登録なしでローカルで使用できます。

<script setup>
import ComponentA from './ComponentA.vue'
</script>

<template>
  <ComponentA />
</template>

<script setup>を使わない場合は下記のように登録する必要があります。

import ComponentA from './ComponentA.js'

export default {
  components: {
    ComponentA
  },
  setup() {
    // ...
  }
}

オブジェクト内のプロパティごとcomponentsに、キーはコンポーネントの登録名になり、値にはコンポーネントの実装が含まれます。上記の例は、ES2015プロパティの省略形を使用しており、次と同等です。

export default {
  components: {
    ComponentA: ComponentA
  }
  // ...
}

ただし、 ローカル登録されたコンポーネントが子孫のコンポーネントでも利用できるようにはならないことに注意してください。上の場合、ComponentA は現在のコンポーネントのみで利用可能になり、その子や子孫のコンポーネントで利用可能になるわけではありません。

<script setup>を使用してコンポーネントをインポートした場合は、登録が必要ありません。インポートした時点でテンプレートでの使用が可能になります。

コンポーネント名での大文字・小文字の使い方

このガイドでは、コンポーネントを登録する際に PascalCase の名前を用いています。これは次の理由によります:

  1. PascalCase の名前は JavaScript の識別子として有効です。そのため、JavaScript でコンポーネントをインポートしたり登録したりするのが容易になります。また、IDE のオートコンプリートも働きやすくなります。
  2. テンプレートで <PascalCase /> を用いると、これがネイティブの HTML 要素ではなく、Vue のコンポーネントであることがより明確になります。また、Vue コンポーネントとカスタムの要素 (Web Components) を区別することも可能になります。

このスタイルは、SFC や文字列テンプレートを合わせて使う時に推奨されるスタイルです。ただし、DOM テンプレート解析の注意点 で説明しているように、DOM テンプレート内では PascalCase のタグが使えません。

幸いなことに、Vue は PascalCase で登録したコンポーネントから kebab-case 形式のタグへの解決をサポートしています。これにより、MyComponent として登録したコンポーネントは、<MyComponent> と <my-component> のどちらを使ってもテンプレート内で参照できます。そのため、テンプレートの出どころに関わらず、JavaScript のコンポーネント登録のコードには同じものを用いることができます。

[Vue入門] Componentsの基礎

コンポーネントの基礎

コンポーネントによって UI を独立した再利用可能なピースに分割し、それぞれのピースを切り離して考えることができるようになります。アプリケーションはネストされたコンポーネントのツリーによって構成されているのが一般的です:

これは、ネイティブの HTML 要素をネストする方法ととてもよく似ていますが、Vue は独自のコンポーネントモデルを実装しており、各コンポーネントのカスタムコンテンツとロジックをカプセル化することができます。 Vue はまた、ネイティブの Web コンポーネントとうまく連携しています。

コンポーネントの定義

ビルドステップを使用する場合は通常、各 Vue コンポーネントは専用のファイルで .vue 拡張子を使用して定義します。これは 単一ファイルコンポーネント(略して SFC)として知られています:

<script>
export default {
  data() {
    return {
      count: 0
    }
  }
}
</script>

<template>
  <button @click="count++">You clicked me {{ count }} times.</button>
</template>

ビルドステップを使用しない場合、Vue コンポーネントは Vue 固有のオプションを含むプレーンな JavaScript オブジェクトとして定義することができます:

export default {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      You clicked me {{ count }} times.
    </button>`
}

テンプレートは、ここで JavaScript の文字列としてインライン化され、Vue がその場でコンパイルします。また、ID セレクターを使って要素を指定(通常はネイティブの <template> 要素)することもできます。 Vue はそのコンテンツをテンプレート・ソースとして使用します。

上記の例では 1 つのコンポーネントを定義し、それを .js ファイルのデフォルトエクスポートとしてエクスポートしていますが、名前付きエクスポートを使用すると、同じファイルから複数のコンポーネントをエクスポートすることができます。

コンポーネントの使用

TIP

このガイドの残りの部分では SFC 構文を使用します。コンポーネントに関するコンセプトは、ビルドステップを使用するかどうかに関係なく、同じものです。サンプルセクションでは、両方のシナリオでのコンポーネントの使い方をお見せしています。

子コンポーネントを使用するには、親コンポーネントでインポートする必要があります。カウントするコンポーネントを ButtonCounter.vue というファイル内に配置したとすると、このコンポーネントはそのファイルのデフォルトエクスポートとして公開されます:

<script>
import ButtonCounter from './ButtonCounter.vue'

export default {
  components: {
    ButtonCounter
  }
}
</script>

<template>
  <h1>Here is a child component!</h1>
  <ButtonCounter />
</template>

インポートしたコンポーネントをテンプレートに公開するには、components オプションでコンポーネントを登録する必要があります。これにより、そのコンポーネントは登録されたキーを使ってタグとして利用できるようになります。

また、コンポーネントをグローバル登録することで、インポートすることなくアプリケーション内のすべてのコンポーネントで利用できるようにすることもできます。グローバル登録とローカル登録のメリットとデメリットは、専用のコンポーネントの登録セクションで説明されています。

コンポーネントは好きなだけ、何度でも再利用可能です:

<h1>Here are many child components!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />

プレイグラウンドで試す

ボタンをクリックすると、それぞれが別の count を維持することに注意してください。これは、コンポーネントを使用するたびに、新しいインスタンスが作成されるからです。

SFC では、ネイティブの HTML 要素と区別するために、子コンポーネントに パスカルケース のタグ名を使用することが推奨されます。ネイティブの HTML のタグ名は大文字小文字を区別しませんが、Vue の SFC はコンパイルされたフォーマットなので、大文字小文字を区別するタグ名を使うことができます。また、タグを閉じるために /> を使用することができます。

テンプレートを DOM で直接作成する場合(例えば、ネイティブの <template> 要素のコンテンツとして)、テンプレートはブラウザのネイティブな HTML パース動作に従います。そのような場合には、ケバブケース を使用してコンポーネントにクロージングタグを明示する必要があります:

<!-- DOM の中にテンプレートが書かれた場合 -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>

Props の受け渡し

ブログを構築する場合、ブログの記事を表示するコンポーネントが必要になるかと思います。すべてのブログ記事が同じレイアウトで表示されるようにしたいのですが、コンテンツは異なっています。このようなコンポーネントは、表示したい特定の記事のタイトルや内容などのデータを渡すことができない限り役に立ちません。そこで props の出番です。

props はコンポーネントに登録できるカスタム属性のことです。ブログ記事コンポーネントにタイトルを渡すには、このコンポーネントが受け取る props のリスト内で props オプション を使って props プロパティの使用を宣言する必要があります:

<!-- BlogPost.vue -->
<script>
export default {
  props: ['title']
}
</script>

<template>
  <h4>{{ title }}</h4>
</template>

props 属性に値が渡されると、その値はコンポーネントインスタンスのプロパティになります。プロパティの値は、他のコンポーネントプロパティと同様に、テンプレートの中やコンポーネントの this コンテキストでアクセスすることができます。

コンポーネントは好きなだけ props を持つことができ、デフォルトでどんな値でも、どの props にも渡すことができます。

props が登録されると、以下のようにカスタム属性としてデータを渡すことができるようになります:

<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />

しかしながら、一般的なアプリケーションでは親コンポーネントに投稿の配列があることが多いでしょう:

export default {
  // ...
  data() {
    return {
      posts: [
        { id: 1, title: 'My journey with Vue' },
        { id: 2, title: 'Blogging with Vue' },
        { id: 3, title: 'Why Vue is so fun' }
      ]
    }
  }
}

このように各コンポーネントをレンダリングしたい場合は、v-for を使用します:

<BlogPost
  v-for="post in posts"
  :key="post.id"
  :title="post.title"
 />

プレイグラウンドで試す

v-bind を使って、動的な props を渡すことができることに注目してください。これは、レンダリングするコンテンツを事前に正確に把握していない場合に特に役立ちます。

props については以上となりますが、このページを読み終え内容に慣れてきたら、後ほど Props の完全ガイドを読みにくることをおすすめします。

イベントのリッスン

<BlogPost> コンポーネントを開発していく中で、いくつかの機能については、親コンポーネントへの通信が必要になるかもしれません。例えば、ブログ記事のテキストを拡大し、ページの残りの部分はデフォルトのサイズのままにしておくアクセシビリティ機能を含めることにするかもしれません。

親コンポーネントの中では、postFontSize という data property を追加することで、この機能をサポートできます:

data() {
  return {
    posts: [
      /* ... */
    ],
    postFontSize: 1
  }
}

これは、テンプレート内で使用することができ、すべてのブログ記事のフォントサイズを制御することができます:

<div :style="{ fontSize: postFontSize + 'em' }">
  <BlogPost
    v-for="post in posts"
    :key="post.id"
    :title="post.title"
   />
</div>

では、<BlogPost> コンポーネントのテンプレートにボタンを追加してみましょう:

<!-- BlogPost.vue, omitting <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button>Enlarge text</button>
  </div>
</template>

ボタンは今のところ何もしませんが、クリックするとすべての投稿のテキストを拡大表示するように親に伝達したいです。この問題を解決するために、コンポーネントインスタンスはカスタムイベントシステムを提供します。親は子コンポーネントインスタンス上の任意のイベントを、ちょうどネィティブの DOM イベントのように v-on または @ で、リッスンするよう選択できます:

<BlogPost
  ...
  @enlarge-text="postFontSize += 0.1"
 />

そして、子コンポーネントは組み込みの $emit メソッドを呼び出し、イベント名を渡すことによって自身のイベントを発行することができます:

<!-- BlogPost.vue, omitting <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button @click="$emit('enlarge-text')">Enlarge text</button>
  </div>
</template>

enlarge-text="postFontSize += 0.1" リスナーのおかげで、親はイベントを受け取り postFontSize の値を更新することができます。

プレイグラウンドで試す

オプションとして emits オプションを使って emit イベントを宣言することができます:

<!-- BlogPost.vue -->
<script>
export default {
  props: ['title'],
  emits: ['enlarge-text']
}
</script>

コンポーネントが発行する全てのイベントをドキュメント化することで、必要に応じてそれらをバリデーションしています。また、これは Vue が暗黙的に子コンポーネントのルート要素にイベントをネイティブリスナーとして適用するのを避けることにもなります。

カスタムコンポーネントについては以上となりますが、このページを読み終え内容に慣れてきたら、後ほどカスタムイベントの完全ガイドを読みにくることをおすすめします。

スロットを使ったコンテンツ配信

HTML 要素と同じように、以下のようにコンポーネントにコンテンツを渡すことができると便利なことがよくあります:

<AlertBox>
  Something bad happened.
</AlertBox>

これは以下のようなレンダリングがされるかもしれません:

これはデモ目的のエラーです

何らかのエラーが発生しました。

これは Vue のカスタム要素 <slot> を用いて実現することができます:

<template>
  <div class="alert-box">
    <strong>This is an Error for Demo Purposes</strong>
    <slot />
  </div>
</template>

<style scoped>
.alert-box {
  /* ... */
}
</style>

上で見たように、コンテンツを配置するプレースホルダーとして <slot> を使う – それだけです。これで完了です!

プレイグラウンドで試す

スロットについては以上となりますが、このページを読み終え内容に慣れてきたら、後ほどスロットの完全ガイドを読みにくることをおすすめします。

動的コンポーネント

ときどきタブ付きインターフェイスのような、コンポーネントを動的な切り替えが役立つ時があります:

プレイグラウンドのサンプルを開く

上記は Vue の <component> 要素の特別な属性 is で実現されています:

<!-- currentTab 変更時にコンポーネントが変わります -->
<component :is="currentTab"></component>

上の例では、:is に渡される値に以下のいずれかを含めることができます:

  • 登録されたコンポーネントの文字列、もしくは
  • 実際にインポートされたコンポーネントオブジェクト

また、is 属性を使って、通常の HTML 要素を作成することもできます。

複数のコンポーネントを <component :is="..."> で切り替えた場合、切り変えられたコンポーネントがアンマウントされます。組み込みの <KeepAlive> コンポーネント を使用すれば、アクティブでないコンポーネントを強制的に “生きて” いる状態にすることができます。

DOM テンプレート解析の注意点

Vue のテンプレートを DOM に直接記述する場合、Vue は DOM からテンプレート文字列を取得する必要があります。これはブラウザのネイティブな HTML パースのふるまいに、いくつかの注意点をもたらします。

TIP

以下で説明する制限事項は、DOM に直接テンプレートを記述する場合にのみ適用されます。以下のソースからの文字列テンプレートを使用する場合は適用されません:

  • 単一ファイルコンポーネント
  • インラインのテンプレート文字列(例: template: '...'
  • <script type="text/x-template">

大文字小文字の区別

HTML タグや属性名は大文字と小文字を区別しないので、ブラウザーはどの大文字も小文字として解釈します。つまり、DOM 内テンプレートを使用する場合、パスカルケースのコンポーネント名、キャメルケースの props 名、v-on イベント名は、すべてケバブケース(ハイフン区切り)を使用する必要があるということになります:

// JavaScript 内ではキャメルケース
const BlogPost = {
  props: ['postTitle'],
  emits: ['updatePost'],
  template: `
    <h3>{{ postTitle }}</h3>
  `
}
<!-- HTML 内ではケバブケース -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>

自己クロージングタグ

これまでのコードサンプルでは、コンポーネントに自己クロージング (self-closing) タグを使用していました:

<MyComponent />

これは、Vue のテンプレートパーサーが /> を、タグの種類に関係なく任意のタグを終了する指示として尊重するためです。

しかし、DOM テンプレートでは必ず明示的なクロージングタグを入れる必要があります:

<my-component></my-component>

これは HTML の仕様では、いくつかの特定の要素でのみ自己クロージングタグの省略が認められているからです。最も一般的なのは <input> と <img> です。他のすべての要素では、自己クロージングタグを省略すると、ネイティブの HTML パーサーは開始タグを終了させなかったと判断します。例えば、次のようなスニペットです:

<my-component /> <!-- ここがクロージングタグのつもりです -->
<span>hello</span>

このようにパースされます:

<my-component>
  <span>hello</span>
</my-component> <!-- ですが、ブラウザーはここでクローズします -->

要素の配置制限

<ul> 、 <ol> 、 <table> 、 <select> など、一部の HTML 要素には内部に表示できる要素に制限があります。例えば <li> などの一部の要素には、 <tr> 、および <option> は特定の要素内にのみ表示できます。

このような制限のある要素でコンポーネントを使用する場合に問題が発生します。例えば:

<table>
  <blog-post-row></blog-post-row>
</table>

カスタムコンポーネント <blog-post-row> は無効なコンテンツとして巻き上げられ、最終的なレンダリング出力でエラーが発生します。回避策として、特別な is 属性 を使用することができます:

<table>
  <tr is="vue:blog-post-row"></tr>
</table>

TIP

ネイティブの HTML 要素で使用する場合、Vue コンポーネントとして解釈されるためには is の値の前に vue: を付けなければなりません。これはネイティブの組み込みのカスタマイズ要素との混同を避けるために必要となります。

DOM テンプレート解析の注意点については、以上で終わりです。

[Vue入門] Template Refs

テンプレート参照

Vue の宣言型レンダリングモデルは、直接的な DOM 操作のほとんどを抽象化してくれます。それでも、基盤の DOM 要素に直接アクセスすることが必要になるケースがまだ存在するかもしれません。次に示す ref という特殊な属性を用いると、それを実現することができます:

<input ref="input">

ref は、v-for の章で説明した key 属性に似た、特殊な属性です。これを使用すると、特定の DOM 要素や子コンポーネントのインスタンスがマウントされた後に、そのインスタンスへの直接の参照を取得することができます。例えば、コンポーネントがマウントされた時にプログラムを使って入力欄にフォーカスを当てたり、ある要素に使用するサードパーティのライブラリーを初期化したりしたい時に便利です。

参照へのアクセス

結果として得られる参照は、以下のように this.$refs で公開されます:

<script>
export default {
  mounted() {
    this.$refs.input.focus()
  }
}
</script>

<template>
  <input ref="input" />
</template>

参照にアクセスできるのは、コンポーネントがマウントされた後に限られることに注意してください。テンプレートの式で $refs.input にアクセスしようとしても、初回のレンダリングでは null になっています。なぜなら、初回のレンダリングが終わった後でないと要素が存在しないためです!

v-for の中の参照

v3.2.25 以降が必要です。

v-for の中で ref を使用すると、結果として得られる参照の値は、対応する要素を格納する配列になります:

<script>
export default {
  data() {
    return {
      list: [
        /* ... */
      ]
    }
  },
  mounted() {
    console.log(this.$refs.items)
  }
}
</script>

<template>
  <ul>
    <li v-for="item in list" ref="items">
      {{ item }}
    </li>
  </ul>
</template>

プレイグラウンドで試す

参照の配列では、元の配列と同じ順序が保証されないことに注意する必要があります。

関数を使った参照

ref 属性は、文字列のキーの代わりに、関数にバインドすることもできます。関数はコンポーネントが更新されるたびに呼び出され、要素の参照をどこに保持するかを柔軟に決めることができます。関数は、第 1 引数として要素への参照を受け取ります:

<input :ref="(el) => { /* el をプロパティまたは ref に保持する */ }">

動的な :ref のバインディングを使っていることに注目してください。これにより、参照の名前を示す文字列ではなく、関数を渡すことが可能になります。要素がアンマウントされると、引数は null になります。もちろん、インライン関数のほかに、メソッドを指定することもできます。

コンポーネントでの参照

このセクションでは、コンポーネントの知識があることが前提となります。読み飛ばして、後で戻ってきても大丈夫です。

ref は子コンポーネントに対して使用することもできます。その場合、以下のように、参照はコンポーネントのインスタンスへの参照になります:

<script>
import Child from './Child.vue'

export default {
  components: {
    Child
  },
  mounted() {
    // this.$refs.child は <Child /> のインスタンスを保持します。
  }
}
</script>

<template>
  <Child ref="child" />
</template>

参照されるインスタンスは子コンポーネントの this と同じになります。これは、親コンポーネントからは子コンポーネントのすべてのプロパティとメソッドに完全にアクセスできることを意味します。そうなると、親と子の間で実装の細かな部分が緊密に結合された状態が作られやすくなってしまいます。したがって、コンポーネントの参照は、絶対に必要と言える場合に限って使用するべきです。ほとんどの場合、まずは標準の props と emit のインターフェースを使って親子間のやり取りを実装することを試みるとよいでしょう。

子インスタンスへのアクセスに制限を設けるには、expose オプションを使用します:

export default {
  expose: ['publicData', 'publicMethod'],
  data() {
    return {
      publicData: 'foo',
      privateData: 'bar'
    }
  },
  methods: {
    publicMethod() {
      /* ... */
    },
    privateMethod() {
      /* ... */
    }
  }
}

上の例では、テンプレート参照を用いてこのコンポーネントを参照する親に、publicData と publicMethod のみへのアクセスを許可します。

[Vue入門] Watchers

Watchers

基本の例

算出プロパティを使うと、派生した値を宣言的に算出することができるようになります。しかしながら、状態の変更に応じて「副作用」を実行する必要とする場合があります。たとえば、DOM が変化する、あるいは非同期処理の結果に基づいて、別の状態にに変更した場合といったものです。

Option API では、watch オプション を使って、リアクティブなプロパティが変更されるたびに関数を実行することができます:

<script setup>
import { ref, watch } from 'vue'

const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')

// watch works directly on a ref
watch(question, async (newQuestion, oldQuestion) => {
  if (newQuestion.indexOf('?') > -1) {
    answer.value = 'Thinking...'
    try {
      const res = await fetch('https://yesno.wtf/api')
      answer.value = (await res.json()).answer
    } catch (error) {
      answer.value = 'Error! Could not reach the API. ' + error
    }
  }
})
</script>

<template>
  <p>
    Ask a yes/no question:
    <input v-model="question" />
  </p>
  <p>{{ answer }}</p>
</template>

//下は<script setup>でないcompostionAPIのやり方

export default {
  data() {
    return {
      question: '',
      answer: 'Questions usually contain a question mark. ;-)'
    }
  },
  watch: {
    // 問題内容が変更されるたびに、関数が実行されます。
    question(newQuestion, oldQuestion) {
      if (newQuestion.indexOf('?') > -1) {
        this.getAnswer()
      }
    }
  },
  methods: {
    async getAnswer() {
      this.answer = 'Thinking...'
      try {
        const res = await fetch('https://yesno.wtf/api')
        this.answer = (await res.json()).answer
      } catch (error) {
        this.answer = 'Error! Could not reach the API. ' + error
      }
    }
  }
}
<p>
  Ask a yes/no question:
  <input v-model="question" />
</p>
<p>{{ answer }}</p>

プレイグラウンドで試す

watch オプションはドットで区切られたパスをキーとして使うこともできます。

export default {
  watch: {
    // 注意 単純なパスのみ対応しています。式は対応していません。
    'some.nested.key'(newValue) {
      // ...
    }
  }
}

Deep Watchers

watch はデフォルトではネストが浅い場合にしか対応していません: そのため、コールバックは監視対象のプロパティに新しい値が割り当てられた場合にしか実行されません。- そのため、ネストしたプロパティの変更があった場合には実行されません。もし、ネストしたすべての変更でコールバックが実行されるようにする場合、deep watcher を使用する必要があります。

const obj = reactive({ count: 0 })

watch(obj, (newValue, oldValue) => {
  // fires on nested property mutations
  // Note: `newValue` will be equal to `oldValue` here
  // because they both point to the same object!
})

obj.count++


//下は<script setup>でないcompostionAPIのやり方


export default {
  watch: {
    someObject: {
      handler(newValue, oldValue) {
        // 注意:オブジェクト自体が置き替わらない限り、
        // ネストした変更では、 `newValue` は、`oldValue` と
        // 等しくなります。
      },
      deep: true
    }
  }
}

使用上の注意

deep watch は、監視対象のオブジェクトのネストされた全てのプロパティをトラバースする必要があるため、大きなデータ構造で使用するときにはコストが高くなります。使用するときは、どうしても必要なときにだけ使用し、パフォーマンスへの影響に注意しましょう。

Eager Watchers

watch は、デフォルトでは、遅延して実行されます: 監視対象の値が変更するまでコールバックは実行されません。しかし、同様のコールバックのロジックを先に実行したい場合もあります。- たとえば、初期値のデータを読み込み、関連する状態が変更されるたび、再びデータを読み込みたいときです。

handler 関数と immediate: true オプションを設定したオブジェクトを利用して宣言することで、監視対象のコールバック関数をすぐ実行させることができます:

export default {
  // ...
  watch: {
    question: {
      handler(newQuestion) {
        // コンポーネントが生成されるとすぐに実行されます。
      },
      // 前倒しして、コールバックの実行を強制します。
      immediate: true
    }
  }
  // ...
}

コールバックが実行されるタイミング

リアクティブな状態が変更されるとき、Vue コンポーネントの更新と生成された watcher コールバックを実行します。

デフォルトでは、ユーザーが生成した watcher のコールバックは Vue コンポーネントが更新される前に呼ばれます。これはつまり、コールバック内で DOM へアクセスしようとすると、DOM は Vue が更新を適用される前の状態です。

もし Vue の更新に watcher コールバック内で DOM へアクセスしたいとき、flush: 'post' オプションで指定する必要があります:

export default {
  // ...
  watch: {
    key: {
      handler() {},
      flush: 'post'
    }
  }
}

this.$watch()

また、$watch() インスタンスメソッド を使用して watcher を強制的に作成することが可能です:

export default {
  created() {
    this.$watch('question', (newQuestion) => {
      // ...
    })
  }
}

これは、条件付きで watcher をセットアップする必要があるときや、ユーザーの相互作用に応じる場合にのみ、何かを監視しないといけないときに役立ちます。これにより、watcher を早い段階で停止することができます。

Watcher の停止

watch オプションを使って宣言した watchers、あるいは $watch() インスタンスメソッドは、オーナーコンポーネントがアンマウントされた自動的に停止します。そのため、多くの場合において、watcher 自体が停止することを心配する必要はありません。

ごくまれに、オーナーコンポーネントがアンマウントされる前に停止する必要がある場合には、$watch() API は次のような関数を返します:

const unwatch = this.$watch('foo', callback)

// ...wathcer が必要なくなったとき:
unwatch()

watchEffect()

watchEffectはlazyで、監視する対象のソースが変わるまでコールバックが起きません。

const url = ref('https://...')
const data = ref(null)

async function fetchData() {
  const response = await fetch(url.value)
  data.value = await response.json()
}

// fetch immediately
fetchData()
// ...then watch for url change
watch(url, fetchData)

動画で紹介したコード

<template>

  <p>敵のHP:{{enemyHP}}</p>
  <p>ヒーローのHP:{{heroHP}}</p>
<button :class="msgClass" @click.prevent="attack">攻撃</button>
<p >{{msg}}</p>
</template>

<script setup>
import { watch, ref, watchEffect } from 'vue';

const enemyHP = ref(100)
const heroHP = ref(40)
const msg = ref("")
const msgClass = ref("")


const attack = () => {
  enemyHP.value = enemyHP.value - Math.floor(Math.random() * 10)
  heroHP.value = heroHP.value - Math.floor(Math.random() * 5)
  msg.value = ""
}

watch(heroHP, (currHP, prevHP) => {
  console.log(currHP, prevHP)
  if (currHP < 20) {
    msg.value = "HPが半分をきった。まずい。"
  }
  if (prevHP - currHP > 3) {
    msg.value = "会心の一撃を喰らった。"
  }
})

watchEffect(async () =>
  heroHP.value == 40 ? msgClass.value = "blue" :
  heroHP.value <= 20 ? msgClass.value = "red" : ""
)
</script>

<style>
.blue {
  color:blue
}

.red {
  color:red
}
</style>

[Vue入門] ライフサイクルフック

各 Vue コンポーネントインスタンスは、生成時に一連の初期化を行います – 例えば、データ監視のセットアップ、テンプレートのコンパイル、インスタンスの DOM へのマウント、データ変更時の DOM の更新が必要になります。その過程で、ライフサイクルフックと呼ばれる関数も実行され、ユーザーは特定の段階で独自のコードを追加することが可能です。

ライフサイクルフックの登録

例えば、mounted フックは、コンポーネントが最初のレンダリングを終了し DOM ノードを生成した後に、コードを実行するのに使用することができます:

export default {
  mounted() {
    console.log(`コンポーネントがマウントされました。`)
  }
}

インスタンスのライフサイクルのさまざまな段階で呼び出されるフックは他にもあり、最も一般的に使用されるのは mounted、 updated および unmounted です。

すべてのライフサイクルフックは、呼び出し元の現在アクティブなインタンスを指す this とともに呼び出されます。これはライフサイクルフックを宣言するときにアロー関数の使用を避けるべきであることを意味します。アロー関数を使用した場合、 this を介してコンポーネントインスタンスにアクセスできなくなるためです。

ライフサイクルダイアグラム

以下は、インスタンスライフサイクルのダイアグラムです。今すべてを完全に理解する必要はありませんが、さらに学習して構築するにつれて、有用なリファレンスになるでしょう。

Vueのライフサイクルフック

[Vue入門] フォーム入力バインディング

フロントエンドでフォームを扱う場合、フォームの入力要素の状態と、対応する JavaScript の状態を同期しなければならないことがよくあります。値のバインディングやイベントリスナーの変更を手動で行うのは面倒です:

<input
  :value="text"
  @input="event => text = event.target.value">

v-model ディレクティブは、上記を単純化するのに役立ちます:

<input v-model="text">

さらに、 v-model は様々な種類の入力や <textarea> 、 <select> 要素の入力で使用することができます。使用する要素に応じて、異なる DOM プロパティとイベントのペアに自動で展開します:

  • text 型の <input> と <textarea> 要素は value プロパティと input イベントを使用します。
  • <input type="checkbox"> と <input type="radio"> は checked プロパティと change イベントを使用します。
  • <select> は value プロパティと change イベントを使用します。

Note

v-model はフォーム要素にある value 、 checked 、 selected 属性の初期値を無視します。 v-model は常に現在バインドされた JavaScript の状態を最終値のソースとして扱います。初期値の宣言は JavaScript 側で、 data オプション を使用して行ってください。

基本的な使い方

テキスト

<p>Message is: {{ message }}</p>
<input v-model="message" placeholder="edit me" />

Message is:

Try it in the Playground

Note

IME を必要とする言語 (中国語、日本語、韓国語など) では、IME による入力中に v-model が更新されないことに気づくでしょう。 もしこれらの更新にも対応したい場合は、 v-model の代わりに input イベントリスナーと value バインディングを使用してください。

複数行テキスト

<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<textarea v-model="message" placeholder="add multiple lines"></textarea>

Multiline message is:

Try it in the Playground

<textarea> 内の補間(マスタッシュが使えない)は機能しないことに注意してください。代わりに v-model を使用してください。

<!-- bad -->
<textarea>{{ text }}</textarea>

<!-- good -->
<textarea v-model="text"></textarea>

チェックボックス

単一のチェックボックス、 boolean 値:

<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>

false

Try it in the Playground

複数のチェックボックスを同じ配列もしくは Set の値にバインドすることもできます:

export default {
  data() {
    return {
      checkedNames: []
    }
  }
}
<div>Checked names: {{ checkedNames }}</div>

<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>

<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>

<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>

Checked names: []JackJohnMike

この場合、 checkedNames 配列には現在チェックされているボックスの値が常に格納されます。

Try it in the Playground

ラジオ

<div>Picked: {{ picked }}</div>

<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label>

<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>

Picked:OneTwo

Try it in the Playground

セレクト

単一選択:

<div>Selected: {{ selected }}</div>

<select v-model="selected">
  <option disabled value="">Please select one</option>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>

Selected:Please select oneABC

Try it in the Playground

Note

もし v-model 式の初期値がどのオプションにもマッチしない場合、 <select> 要素は “unselected” 状態でレンダリングされます。 iOS では、このような場合に change イベントが発火しないため、ユーザーは最初のアイテムを選択できないことになります。したがって、上記の例のように、空の値を持つ disabled オプションを提供することが推奨されます。

複数選択(配列へのバインド):

<div>Selected: {{ selected }}</div>

<select v-model="selected" multiple>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>

Selected: []ABC

Try it in the Playground

セレクトオプションは v-for で動的にレンダリングすることができます:

export default {
  data() {
    return {
      selected: 'A',
      options: [
        { text: 'One', value: 'A' },
        { text: 'Two', value: 'B' },
        { text: 'Three', value: 'C' }
      ]
    }
  }
}
<select v-model="selected">
  <option v-for="option in options" :value="option.value">
    {{ option.text }}
  </option>
</select>

<div>Selected: {{ selected }}</div>

Try it in the Playground

値のバインディング

ラジオやチェックボックス、セレクトオプションにおいて、 v-model でバインディングされる値は通常は静的な文字列です (またチェックボックスでは真偽値も):

<!-- チェックされているとき `picked` は文字列 "a" -->
<input type="radio" v-model="picked" value="a" />

<!-- `toggle` は true か false のいずれか -->
<input type="checkbox" v-model="toggle" />

<!-- 最初のオプションが選択されているとき `selected` は文字列 "abc" -->
<select v-model="selected">
  <option value="abc">ABC</option>
</select>

しかし時には現在アクティブなインスタンスの動的プロパティに値をバインドしたいことがあります。それには v-bind を使用することができます。さらに、 v-bind を使用することで文字列以外の値も入力値にバインドすることができます。

チェックボックス#

<input
  type="checkbox"
  v-model="toggle"
  true-value="yes"
  false-value="no" />

true-value と false-value は v-model においてのみ機能する Vue 特有の属性です。ここでは toggle プロパティの値はボックスがチェックされると 'yes' がセットされ、チェックが外されると 'no' がセットされます。 v-bind を使用して動的な値にバインドすることもできます。

<input
  type="checkbox"
  v-model="toggle"
  :true-value="dynamicTrueValue"
  :false-value="dynamicFalseValue" />

Tip

ブラウザはチェックされていないボックスをフォームの送信には含めないため、 true-value と false-value 属性は入力の value 属性に影響を与えません。 2 つの値 (例、 “yes” もしくは “no” ) のうち 1 つが送信されることを保証するには、代わりにラジオを使用してください。

ラジオ

<input type="radio" v-model="pick" :value="first" />
<input type="radio" v-model="pick" :value="second" />

pick には、 1 つ目のラジオがチェックされると first の値がセットされ、 2 つ目のラジオがチェックされると second の値がセットされます。

セレクトオプション

<select v-model="selected">
  <!-- インラインのオブジェクトリテラル -->
  <option :value="{ number: 123 }">123</option>
</select>

v-model は文字列でない値のバインディングもサポートしています! 上記の例では、オプションが選択されると、 selected にはオブジェクトリテラル値である { number: 123 } がセットされます。

修飾子

.lazy

デフォルトでは、 v-model は各 input イベントの後に、入力とデータを同期します (上記 の IME による入力は例外とします)。 代わりに change イベント後に同期する lazy 修飾子を追加することができます。

<!-- "input" の代わりに "change" イベント後に同期されます -->
<input v-model.lazy="msg" />

.number

ユーザー入力を自動で数値として型変換したい場合、 v-model で管理している入力に number 修飾子を追加することができます。

<input v-model.number="age" />

もし値が parseFloat() で解析できない場合は、代わりに元の値が使用されます。

input が type="number" を持つ場合は number 修飾子が自動で適用されます。

.trim

ユーザー入力から自動で空白を取り除きたい場合、 v-model で管理している入力に trim 修飾子を追加することができます。

[Vue入門] イベントハンドリングとメソッド

イベントの扱い

v-on ディレクティブを使用することで、 DOM イベントの実行やイベントトリガー時にいくつかの JavaScript を実行します。これは通常 @ に省略することができます。使い方は v-on:click="handler"、あるいは省略して @click="handler" として使用します。

ハンドラーの値は以下のいずれかを指定します:

  1. インラインハンドラー: イベント発火時に実行されるインライン JavaScript 式 (これはネイディブの onclick 属性に似たものです)
  2. メソッドハンドラー: コンポーネント上で定義されたメソッドを示すプロパティ名またはパス

インラインハンドラー

インラインハンドラーは、通常、次のような単純なケースで使用されます:

data() {
  return {
    count: 0
  }
}
<button @click="count++">Add 1</button>
<p>Count is: {{ count }}</p>

プレイグラウンドで試す

メソッドハンドラー

しかしながら、多くのイベントハンドラーのロジックはより複雑で、インラインハンドラーでは実行できない可能性があります。だからこそ、v-on は呼び出したいコンポーネントメソッドの名前やパスで使用することができます。

例:

data() {
  return {
    name: 'Vue.js'
  }
},
methods: {
  greet(event) {
    // メソッド内の `this` は、現在、アクティブなインスタンスを示します。
    alert(`Hello ${this.name}!`)
    // `event` はネイティブの DOM イベントです。
    if (event) {
      alert(event.target.tagName)
    }
  }
}
<!-- `greet` は上で定義したメソッド名です。 -->
<button @click="greet">Greet</button>

プレイグラウンドで試す

メソッドハンドラーは、トリガーとなるネイティブの DOM イベントオブジェクトを自動的に受け取ります。- 上記の例では、event.target.tagName を通してイベントを発信した要素へアクセスすることができます。

メソッド 対 インライン検出

テンプレートコンパイラーは v-on は、文字列値が JavaScript identifier、あるいはプロパティのアクセスパスか適切などうかを検証することで、メソッドハンドラーを検出します。例えば、foo、 foo.bar さらには foo['bar'] がメソッドハンドラーとして扱われる一方、foo() and count++ はインラインハンドラーとして扱われます。

インラインハンドラー下でのメソッドの呼び出し

メソッドネームに直接束縛する代わりに、インラインハンドラーのメソッドを呼び出すこともできます。これにより、ネイティブイベントの代わりにカスタムの引数をメソッドに渡すことができます。

methods: {
  say(message) {
    alert(message)
  }
}
<button @click="say('hello')">Say hello</button>
<button @click="say('bye')">Say bye</button>

プレイグラウンドで試す

インラインハンドラーのイベント引数へのアクセス

ときどき、インラインハンドラーでオリジナルの DOM イベントへアクセスする必要な場合もあります。その場合、特別な $event 変数を使用するメソッドに渡したり、あるいはインライン上でアロー関数を使用します:

<!-- 特殊変数 $event を使用する場合 -->
<button @click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>

<!-- インラインでアロー関数を使用する場合 -->
<button @click="(event) => warn('Form cannot be submitted yet.', event)">
  Submit
</button>
methods: {
  warn(message, event) {
    // これでネイティブイベントにアクセスできるようになりました
    if (event) {
      event.preventDefault()
    }
    alert(message)
  }
}

イベント修飾子(モディファイヤー)

イベントハンドラーの中で event.preventDefault() あるいは event.stopPropagation() を呼び出す必要があるのはよくあることです。たとえメソッド内で簡単に扱うことができるかどうかにかかわらず、メソッドが DOM イベントの詳細を扱うのではなく、純粋にデータロジックに特化したメソッドがより最適です。

この問題を扱うにあたり、Vue は v-on のための イベント修飾子(event modifiers) を提供します。修飾子は、ドット (.) によって示されるディレクティブの接頭辞であることを思い返してください。

  • .stop
  • .prevent
  • .self
  • .capture
  • .once
  • .passive
<!-- クリックイベントの伝搬は停止します -->
<a @click.stop="doThis"></a>

<!-- サブミットイベントはページをリロードしません -->
<form @submit.prevent="onSubmit"></form>

<!-- 修飾子は繋げることができます -->
<a @click.stop.prevent="doThat"></a>

<!-- ただの修飾子として使用できます -->
<form @submit.prevent></form>

<!-- event.target が 要素それ自身であるときだけ ハンドラーが呼び出されます-->
<!-- つまり、子要素である場合 -->
<div @click.self="doThat">...</div>

TIP

関連するコードが同じの順番で生成されるため、修飾子を使用するときには順番は重要です。したがって、@click.prevent.self を使うと 要素自身とその子要素に対するクリックのデフォルトアクション に干渉するのに対して、@click.self.prevent は要素自身のクリックのデフォルトアクションにのみに干渉します。

.capture、 .once、 さらには .passive 修飾子は ネイティブ addEventListener メソッドのオプション を反映します:

<!-- イベントリスナーを加えるときはキャプチャーモードを使用してください。 -->
<!-- つまり、内側の要素をターゲットにしたイベントはその要素で操作される前にここで操作されます。 -->
<div @click.capture="doThis">...</div>

<!-- クリックイベントは最大で1回は呼ばれます。 -->
<a @click.once="doThis"></a>

<!-- `event.preventDefault()` が含まれる場合、 -->
<!-- `onScroll`が完了するのを待っている代わりに -->
<!-- スクロールイベントのデフォルト動作 (scrolling) が即座に実行されます -->
<div @scroll.passive="onScroll">...</div>

.passive 修飾子は通常、モバイル機器のパフォーマンスの改善 のためのタッチイベントリスナーで使用します。

TIP

.passive と .prevent を一緒に使わないでください。なぜなら、.passive はブラウザーですでにイベントのデフォルト動作を干渉「しない」ことを示しているからです。それにより、もしそうした場合においてブラウザーが警告を出す可能性が高いからです。

キー修飾子

イベントの購読#

v-on ディレクティブを使用することで、 DOM イベントの購読やイベント発火時にいくつかの JavaScript を実行します。これは通常 @ に省略することができます。使い方は v-on:click="handler"、あるいは省略して @click="handler" として使用します。

ハンドラーの値は以下のいずれかを指定します:

  1. インラインハンドラー: イベント発火時に実行されるインライン JavaScript 式 (これはネイディブの onclick 属性に似たものです)
  2. メソッドハンドラー: コンポーネント上で定義されたメソッドを示すプロパティ名またはパス

インラインハンドラー

インラインハンドラーは、通常、次のような単純なケースで使用されます:

data() {
  return {
    count: 0
  }
}
<button @click="count++">Add 1</button>
<p>Count is: {{ count }}</p>

プレイグラウンドで試す

メソッドハンドラー

しかしながら、多くのイベントハンドラーのロジックはより複雑で、インラインハンドラーでは実行できない可能性があります。だからこそ、v-on は呼び出したいコンポーネントメソッドの名前やパスで使用することができます。

例:

data() {
  return {
    name: 'Vue.js'
  }
},
methods: {
  greet(event) {
    // メソッド内の `this` は、現在、アクティブなインスタンスを示します。
    alert(`Hello ${this.name}!`)
    // `event` はネイティブの DOM イベントです。
    if (event) {
      alert(event.target.tagName)
    }
  }
}
<!-- `greet` は上で定義したメソッド名です。 -->
<button @click="greet">Greet</button>

プレイグラウンドで試す

メソッドハンドラーは、トリガーとなるネイティブの DOM イベントオブジェクトを自動的に受け取ります。- 上記の例では、event.target.tagName を通してイベントを発信した要素へアクセスすることができます。

参照: イベントハンドラーの型付け 

メソッド 対 インライン検出#

テンプレートコンパイラーは v-on は、文字列値が JavaScript identifier、あるいはプロパティのアクセスパスか適切などうかを検証することで、メソッドハンドラーを検出します。例えば、foo、 foo.bar さらには foo['bar'] がメソッドハンドラーとして扱われる一方、foo() and count++ はインラインハンドラーとして扱われます。

インラインハンドラー下でのメソッドの呼び出し#

メソッドネームに直接束縛する代わりに、インラインハンドラーのメソッドを呼び出すこともできます。これにより、ネイティブイベントの代わりにカスタムの引数をメソッドに渡すことができます。

methods: {
  say(message) {
    alert(message)
  }
}
<button @click="say('hello')">Say hello</button>
<button @click="say('bye')">Say bye</button>

プレイグラウンドで試す

インラインハンドラーのイベント引数へのアクセス#

ときどき、インラインハンドラーでオリジナルの DOM イベントへアクセスする必要な場合もあります。その場合、特別な $event 変数を使用するメソッドに渡したり、あるいはインライン上でアロー関数を使用します:

<!-- 特殊変数 $event を使用する場合 -->
<button @click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>

<!-- インラインでアロー関数を使用する場合 -->
<button @click="(event) => warn('Form cannot be submitted yet.', event)">
  Submit
</button>
methods: {
  warn(message, event) {
    // これでネイティブイベントにアクセスできるようになりました
    if (event) {
      event.preventDefault()
    }
    alert(message)
  }
}

イベント修飾子

イベントハンドラーの中で event.preventDefault() あるいは event.stopPropagation() を呼び出す必要があるのはよくあることです。たとえメソッド内で簡単に扱うことができるかどうかにかかわらず、メソッドが DOM イベントの詳細を扱うのではなく、純粋にデータロジックに特化したメソッドがより最適です。

この問題を扱うにあたり、Vue は v-on のための イベント修飾子(event modifiers) を提供します。修飾子は、ドット (.) によって示されるディレクティブの接頭辞であることを思い返してください。

  • .stop
  • .prevent
  • .self
  • .capture
  • .once
  • .passive
<!-- クリックイベントの伝搬は停止します -->
<a @click.stop="doThis"></a>

<!-- サブミットイベントはもはやページをリロードしません -->
<form @submit.prevent="onSubmit"></form>

<!-- 修飾子は繋げることができます -->
<a @click.stop.prevent="doThat"></a>

<!-- ただの修飾子として使用できます -->
<form @submit.prevent></form>

<!-- event.target が 要素それ自身であるときだけ ハンドラーが呼び出されます-->
<!-- つまり、子要素である場合 -->
<div @click.self="doThat">...</div>

TIP

関連するコードが同じの順番で生成されるため、修飾子を使用するときには順番は重要です。したがって、@click.prevent.self を使うと 要素自身とその子要素に対するクリックのデフォルトアクション に干渉するのに対して、@click.self.prevent は要素自身のクリックのデフォルトアクションにのみに干渉します。

.capture、 .once、 さらには .passive 修飾子は ネイティブ addEventListener メソッドのオプション を反映します:

<!-- イベントリスナーを加えるときはキャプチャーモードを使用してください。 -->
<!-- つまり、内側の要素をターゲットにしたイベントはその要素で操作される前にここで操作されます。 -->
<div @click.capture="doThis">...</div>

<!-- クリックイベントは最大で1回は呼ばれます。 -->
<a @click.once="doThis"></a>

<!-- `event.preventDefault()` が含まれる場合、 -->
<!-- `onScroll`が完了するのを待っている代わりに -->
<!-- スクロールイベントのデフォルト動作 (scrolling) が即座に実行されます -->
<div @scroll.passive="onScroll">...</div>

.passive 修飾子は通常、モバイル機器のパフォーマンスの改善 のためのタッチイベントリスナーで使用します。

TIP

.passive と .prevent を一緒に使わないでください。なぜなら、.passive はブラウザーですでにイベントのデフォルト動作を干渉「しない」ことを示しているからです。それにより、もしそうした場合においてブラウザーが警告を出す可能性が高いからです。

キー修飾子(モディファイヤー)

キーボードイベントを購読しているとき、特定のキーをチェックする必要がある場合があります。キーイベントを受信しているとき、Vue は v-on あるいは @ にキー修飾子を加えることができます:

<!--`vm.submit()`は `key` が `Enter`のときにのみ呼ばれます -->
<input @keyup.enter="submit" />

KeyboardEvent.key を介して公開されている有効なキーネームをケバブケースに変換されることで、直接修飾子として使用することができます。

<input @keyup.page-down="onPageDown" />

上記の例では、$event.key が 'PageDown' が等しい場合にのみハンドラーは呼ばれます。

キーのエリアス

Vue はもっともよく使われるキーのためにエリアスが提供されます:

  • .enter
  • .tab
  • .delete ( “Delete” と “Backspace” キーの両方をキャプチャします )
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

システムの修飾子

以下の修飾子を使用すると、対応する修飾子が押されたときにのみ、マウスやキーボードのイベントがが発火することができます:

  • .ctrl
  • .alt
  • .shift
  • .meta

注意

Mac キーボードでは、メタキーはコマンドキー (⌘) が使われるのに対して、Windows キーボードでは、メタキーはウィンドウズキー (⊞) が使われる。サンマイクロシステムキーボードでは、メタキーは実線のダイアモンド (◆) がマークとして使われます。特定のキーボード、特に MIT や Lisp マシーンキーボードや、ナイトキーボードやスペースカデットキーボードといった後継機では、メタキーは「META」、または「Meta」と表示されます。

例:

<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />

<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>

TIP

修飾子キーは通常のキーとは異なり、keyup イベントと一緒に使用する時は、イベントが発生した時に押されているる必要があることに注意してください。言い換えると、ctrl キーを押し続けている間は keyup.ctrl はキーを離した時にのみ発火されます。ctrl キーだけをを解放したとしてもイベントは発火しません。

.exact 修飾子

.exact 修飾子は、イベントを発動するのに必要なシステム修飾子との正確な組み合わせのコントロールを可能にします。

<!-- これは  たとえ Alt や Shift キーが押されてたとしても発火されます -->
<button @click.ctrl="onClick">A</button>

<!-- これは Ctrl キーが押され、他のキーが押されてないときだけ発行されます -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- これは システム修飾子が押されてないときだけ発行されます -->
<button @click.exact="onClick">A</button>

マウスボタン修飾子

  • .left
  • .right
  • .middle

これらの修飾子は特定のマウスボタンが発火したイベントを制御するハンドラーを制限します。

キーボードイベントを購読しているとき、特定のキーをチェックする必要がある場合があります。キーイベントを受信しているとき、Vue は v-on あるいは @ にキー修飾子を加えることができます:

<!--`vm.submit()`は `key` が `Enter`のときにのみ呼ばれます -->
<input @keyup.enter="submit" />

KeyboardEvent.key を介して公開されている有効なキーネームをケバブケースに変換されることで、直接修飾子として使用することができます。

<input @keyup.page-down="onPageDown" />

上記の例では、$event.key が 'PageDown' が等しい場合にのみハンドラーは呼ばれます。

キーのエリアス

Vue はもっともよく使われるキーのためにエリアスが提供されます:

  • .enter
  • .tab
  • .delete ( “Delete” と “Backspace” キーの両方をキャプチャします )
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

システムの修飾子

以下の修飾子を使用すると、対応する修飾子が押されたときにのみ、マウスやキーボードのイベントがが発火することができます:

  • .ctrl
  • .alt
  • .shift
  • .meta

注意

Mac キーボードでは、メタキーはコマンドキー (⌘) が使われるのに対して、Windows キーボードでは、メタキーはウィンドウズキー (⊞) が使われる。サンマイクロシステムキーボードでは、メタキーは実線のダイアモンド (◆) がマークとして使われます。特定のキーボード、特に MIT や Lisp マシーンキーボードや、ナイトキーボードやスペースカデットキーボードといった後継機では、メタキーは「META」、または「Meta」と表示されます。

例:

<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />

<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>

TIP

修飾子キーは通常のキーとは異なり、keyup イベントと一緒に使用する時は、イベントが発生した時に押されているる必要があることに注意してください。言い換えると、ctrl キーを押し続けている間は keyup.ctrl はキーを離した時にのみ発火されます。ctrl キーだけをを解放したとしてもイベントは発火しません。

.exact 修飾子

.exact 修飾子は、イベントを発火するのに必要なシステム修飾子との正確な組み合わせのコントロールを可能にします。

<!-- これは  たとえ Alt や Shift キーが押されてたとしても発火されます -->
<button @click.ctrl="onClick">A</button>

<!-- これは Ctrl キーが押され、他のキーが押されてないときだけ発行されます -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- これは システム修飾子が押されてないときだけ発行されます -->
<button @click.exact="onClick">A</button>

マウスボタン修飾子

  • .left
  • .right
  • .middle

これらの修飾子は特定のマウスボタンがトリガーしたイベントを制御するハンドラーを制限します。