[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

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

[Vue入門] リストレンダリング

v-for

配列に基づいて項目のリストをレンダリングするには、v-for ディレクティブを使用します。v-for ディレクティブでは、item in items という形式の特別な構文が必要になります。ここで、items は元のデータの配列を指し、item は反復処理の対象となっている配列要素のエイリアスを指します:

const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<li v-for="item in items">
  {{ item.message }}
</li>

v-for のスコープ内では、テンプレート内の式から親スコープのすべてのプロパティにアクセスできます。さらに、v-for では以下のように現在の項目のインデックスを指す、2 つ目の省略可能なエイリアスもサポートされています:

const parentMessage = ref('Parent')
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<li v-for="(item, index) in items">
  {{ parentMessage }} - {{ index }} - {{ item.message }}
</li>

Parent – 0 – FooParent – 1 – Bar

プレイグラウンドで試す

v-for の変数のスコープは、次の JavaScript と同様です:

const parentMessage = 'Parent'
const items = [
  /* ... */
]

items.forEach((item, index) => {
  // ここからスコープの外の `parentMessage` にはアクセスできますが、
  // `item` と `index` はこの中でしか使用できません。
  console.log(parentMessage, item.message, index)
})

v-for の値が forEach のコールバック関数のシグネチャと一致している様子に注目してください。実際、関数の引数で分割代入を使用するときと同様に、v-for の item のエイリアスでも分割代入を使用することができます:

<li v-for="{ message } in items">
  {{ message }}
</li>

<!-- index のエイリアスを伴う場合 -->
<li v-for="({ message }, index) in items">
  {{ message }} {{ index }}
</li>

ネストされた v-for でも、スコープの挙動はネストされた関数と同様です。以下のように、それぞれの v-for のスコープでは親のスコープにアクセスできます:

<li v-for="item in items">
  <span v-for="childItem in item.children">
    {{ item.message }} {{ childItem }}
  </span>
</li>

区切り文字として in の代わりに of を使用して、JavaScript のイテレーター構文に近付けることもできます:

<div v-for="item of items"></div>

v-for をオブジェクトに適用する

v-for は、オブジェクトの各プロパティを反復処理するのにも使用できます。

const myObject = reactive({
  title: 'How to do lists in Vue',
  author: 'Jane Doe',
  publishedAt: '2016-04-10'
})
<ul>
  <li v-for="value in myObject">
    {{ value }}
  </li>
</ul>

以下のように 2 つ目のエイリアスを指定すると、プロパティの名前 (「キー」とも呼ばれる) を取り出すことができます:

<li v-for="(value, key) in myObject">
  {{ key }}: {{ value }}
</li>

さらに 3 つ目のエイリアスを追加すると、インデックスを取り出せます:

<li v-for="(value, key, index) in myObject">
  {{ index }}. {{ key }}: {{ value }}
</li>

プレイグラウンドで試す

注意

オブジェクトを反復処理する際の順序は Object.keys() による列挙に基づきますが、JavaScript エンジンの異なる実装間での一貫性は保証されていません。

v-for で範囲を使用する

v-for は、整数を取ることもできます。その場合、1...n のような範囲に従って、その回数だけテンプレートが繰り返されます。

<span v-for="n in 10">{{ n }}</span>

n の値は 0 ではなく 1 から始まることに注意してください。

<template> に v-for を適用する

テンプレートに v-if を適用する場合と同様に、 <template> タグに v-for を適用すると、複数の要素からなるブロックをレンダリングできます。例:

<ul>
  <template v-for="item in items">
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

v-for と v-if を組み合わせる場合

注意

暗黙の優先順位があるため、v-if と v-for を同一の要素に対して使用することは推奨されません。詳しくはスタイルガイドを参照してください。

同じノードに両方が存在する場合、v-for よりも v-if のほうが優先順位が高くなります。これは、以下のように v-for のスコープにある変数には v-if の条件式からアクセスできないことを意味します:

<!--
"todo" というプロパティがインスタンスで未定義となるため、
エラーがスローされます。
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo.name }}
</li>

この問題は、以下のようにラップ用の <template> タグを設けて、そこに v-for を移動することで解決できます (このほうがより明示的でもあります):

<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>

key による状態管理

v-for でレンダリングされた要素のリストを Vue が更新するとき、デフォルトでは「その場での修繕」(in-place patch) という戦略が用いられます。データ項目の順序が変更された場合、Vue は項目の順序に合うように DOM 要素を移動させるのではなく、個々の要素をその位置のままで修正し、各インデックスでレンダリングされるべきものを反映させます。

このデフォルトのモードは効率性が高いものの、これが適すのは、リストのレンダリング出力が子コンポーネントの状態や一時的な DOM の状態 (フォームの入力値など) に依存しない場合に限られます

Vue に各ノードを一意に追跡するためのヒントを与え、既存の要素を再利用して並べ替えを適用できるようにするには、以下のように各項目に一意の key 属性を指定する必要があります:

<div v-for="item in items" :key="item.id">
  <!-- 内容 -->
</div>

<template v-for> を例に取ると、key は以下のように <template> の中に置きます:

<template v-for="todo in todos" :key="todo.name">
  <li>{{ todo.name }}</li>
</template>

注意

ここでいう key は、v-bind でバインドされる特別な属性です。v-for をオブジェクトに適用するときのプロパティのキーの変数と混同しないように注意してください。

v-for の key 属性は、可能な場合は必ず指定することが推奨されます。ただし、反復処理する DOM の内容が単純なものである (つまりコンポーネントやステートフルな DOM 要素を含まない) 場合、またはパフォーマンス向上のために意図的にデフォルト動作を用いたい場合は、この限りではありません。

key のバインディングにはプリミティブ型の値、つまり文字列と数値が想定されます。v-for の key にオブジェクトを指定してはいけません。key 属性の詳しい使い方については、key API のドキュメントを参照してください。

v-for をコンポーネントに適用する#

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

通常の要素と同様に、コンポーネントにも v-for を直接適用することができます (key を指定するのを忘れないでください):

<my-component v-for="item in items" :key="item.id"></my-component>

ただし、これだけではデータが自動的にコンポーネントに渡されるようにはなりません。なぜなら、コンポーネントはそれ自身の独立したスコープを持つからです。コンポーネントに反復処理対象のデータを渡すには、以下のようにプロパティを併用する必要があります:

<my-component
  v-for="(item, index) in items"
  :item="item"
  :index="index"
  :key="item.id"
></my-component>

item が自動的に注入されないようになっている理由は、そうしてしまうと、コンポーネントが v-for の動作と密に結合してしまうためです。データの供給源を明示的に指定することにより、コンポーネントが別の場面でも再利用できるような作りになっています。

シンプルな ToDo リストのサンプルで、v-for でコンポーネントのリストをレンダリングするとき、各インスタンスに異なるデータをどのように渡せばよいかを確認できます。

配列の変更の検出

ミューテーションメソッド

Vue は監視対象の配列のミューテーションメソッドをラップして、これらのメソッドでビューの更新が一緒にトリガーされるようにしています。Vue がラップしているメソッドは次の通りです:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

配列の置き換え

ミューテーションメソッドはその名が示す通り、呼び出し元の配列を変化させるメソッドです。これに対し、filter()concat()slice() など、呼び出し元の配列を変化させないメソッドもあります。これらのメソッドは常に新しい配列を返します。ミューテーションしないメソッドを扱う場合は、以下のように、古い配列を新しい配列に置き換える必要があります:

// `item` は配列値の参照です
items.value = items.value.filter((item) => item.message.match(/Foo/))

このようにすると、Vue が既存の DOM を破棄してリスト全体を再レンダリングするように思えるかもしれませんが、幸いにもそのようなことはありません。Vue には DOM 要素を最大限に再利用するためのスマートな発見的アルゴリズムが実装されているため、既存の配列を、重複するオブジェクトが含まれる新しい配列に置き換える場合でも、非常に効率的な処理が行われます。

フィルタリング/並べ替えの結果を表示する

時には、配列の元のデータを実際に変更することやリセットすることなしに、フィルタリングや並べ替えを適用したバージョンを表示したいことがあります。そのような場合には、フィルタリングや並べ替えを適用した配列を返す算出プロパティを作成することができます。

例:

const numbers = ref([1, 2, 3, 4, 5])

const evenNumbers = computed(() => {
  return numbers.value.filter((n) => n % 2 === 0)
})
<li v-for="n in evenNumbers">{{ n }}</li>

算出プロパティが使えない場所 (例えばネストされた v-for ループの内側) では、以下のようにメソッドを使用できます:

const sets = ref([
  [1, 2, 3, 4, 5],
  [6, 7, 8, 9, 10]
])

function even(numbers) {
  return numbers.filter((number) => number % 2 === 0)
}
<ul v-for="numbers in sets">
  <li v-for="n in even(numbers)">{{ n }}</li>
</ul>

算出プロパティの中で reverse() と sort() を使用するときは注意してください!これら 2 つのメソッドには、算出プロパティのゲッターの中では避けるべき、元の配列を変更するという作用があります。以下のように、これらのメソッドを呼び出す前には元の配列のコピーを作成します:

- return numbers.reverse()
+ return [...numbers].reverse()

[Vue入門] 条件付きレンダリング

v-if

v-if ディレクティブは、ブロックを条件に応じてレンダリングしたい場合に使用されます。ブロックは、ディレクティブの式が真を返す場合のみレンダリングされます。

<h1 v-if="awesome">Vue is awesome!</h1>

v-else

v-if に対して “else block” を示すために、v-else ディレクティブを使用できます:

<button @click="awesome = !awesome">Toggle</button>

<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>

Toggle

Vue is awesome!

Try it in the Playground

v-else 要素は、v-if または v-else-if 要素の直後になければなりません。それ以外の場合は認識されません。

v-else-if

v-else-if は、名前が示唆するように、v-if の “else if block” として機能します。また、複数回連結することもできます:

<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>

v-else と同様に、v-else-if 要素は v-if 要素または v-else-if 要素の直後になければなりません。

<template> での v-if

v-if はディレクティブなので、単一の要素に付加する必要があります。しかし、1 要素よりも多くの要素と切り替えたい場合はどうでしょうか?このケースでは、非表示ラッパー (wrapper) として提供される、<template> 要素で v-if を使用できます。最終的にレンダリングされる結果は、<template> 要素は含まれません。

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>

v-else と v-else-if は <template> でも使用可能です。

v-show

条件的に要素を表示するための別のオプションは v-show です。使用方法はほとんど同じです:

<h1 v-show="ok">Hello!</h1>

違いは v-show による要素は常レンダリングされて DOM に残るということです。v-show はシンプルに要素の display CSS プロパティを切り替えます。

v-show は <template> 要素をサポートせず、v-else とも連動しないということに注意してください。

v-if vs v-show

v-if は、イベントリスナと子コンポーネント内部の条件ブロックが適切に破棄され、そして切り替えられるまでの間再作成されるため、”リアル”な条件レンダリングです。

v-if はまた 遅延レンダリング (lazy) でもあります。 初期表示において状態が false の場合、何もしません。つまり条件付きブロックは、条件が最初に true になるまでレンダリングされません。

一方で、v-show はとてもシンプルです。要素は初期条件に関わらず常にレンダリングされ、シンプルな CSS ベースの切り替えによって表示されます。

一般的に、v-if はより高い切り替えコストを持っているのに対して、 v-show はより高い初期レンダリングコストを持っています。 そのため、とても頻繁に何かを切り替える必要があれば v-show を選び、条件が実行時に変更することがほとんどない場合は、v-if を選びます。

v-if と v-for

Note

暗黙的な優先順位により、 v-if と v-for を同じ要素で利用することは 推奨されません。 詳細については スタイルガイド を参照ください。

v-if と v-for が同じ要素に両方つかわれる場合、 v-if が先に評価されます。

[Vue入門] クラスとスタイルのバインディング

データバインディングは、HTML 要素に持たせる CSS クラスのリストやインラインのスタイルを自在に操作したいという、よくあるニーズに応えます。どちらも属性なので v-bind で扱うことができ、あとは式で最終的な文字列を算出すればよいだけです。しかし、文字列の結合に手を出すのは、手間がかかり、間違いが起きやすくなるものです。そこで、Vue では class や style に対して v-bind を用いるとき、特別な拡張が利用できるようになっています。文字列のほかに、オブジェクトまたは配列として評価される式も利用できます。

HTML クラスのバインディング

オブジェクトへのバインディング

:class (v-bind:class の省略記法) では、オブジェクトを渡して CSS クラスを動的に切り替えることができます:

<div :class="{ active: isActive }"></div>

上の構文は、コンポーネントのデータの isActive というプロパティが真値(true)であるかどうかによって active という CSS クラスを含めるかどうかを決定する、という意味になります。

オブジェクトのフィールドを増やせば、複数のクラスをトグルすることができます。さらに、:class ディレクティブは通常の class 属性と共存させることもできます。例えば、次のような状態があるとします:

const isActive = ref(true)
const hasError = ref(false)

そしてテンプレートが次のようになっているとします:

<div
  class="static"
  :class="{ active: isActive, 'text-danger': hasError }"
></div>

このとき、レンダリング結果は次のようになります:

<div class="static active"></div>

isActive や hasError が変化すると、それに合わせてクラスのリストも更新されます。例えば、hasError が true になればクラスのリストは "static active text-danger" に変わります。

バインドするオブジェクトはインラインにしなくても構いません:

const classObject = reactive({
  active: true,
  'text-danger': false
})
<div :class="classObject"></div>

これも、同じレンダリング結果を得られます。オブジェクトを返す算出プロパティにクラスをバインドすることも可能です。次の例は、よく使われる強力なパターンです:

const isActive = ref(true)
const error = ref(null)

const classObject = computed(() => ({
  active: isActive.value && !error.value,
  'text-danger': error.value && error.value.type === 'fatal'
}))
<div :class="classObject"></div>

配列へのバインディング

次のように :class を配列にバインドすると、クラスのリストを適用することができます:

const activeClass = ref('active')
const errorClass = ref('text-danger')
<div :class="[activeClass, errorClass]"></div>

レンダリング結果は次のようになります:

<div class="active text-danger"></div>

リストに含まれる特定のクラスを条件に基づいて切り替えたい場合には、三項演算子を使えば実現できます:

<div :class="[isActive ? activeClass : '', errorClass]"></div>

この場合、errorClass は常に適用され、activeClass は isActive が真のときだけ適用されます。

しかし、条件を付けたいクラスが複数あると、これでは少し冗長になります。そこで、配列構文の中でオブジェクト構文を使うこともできるようになっています:

<div :class="[{ active: isActive }, errorClass]"></div>

コンポーネントでの使用

このセクションは、コンポーネントについての知識があることを前提としています。スキップして、後から読み直すのでも大丈夫です。

ルート要素が 1 つだけのコンポーネントで class 属性を使用すると、そこで指定したクラスがコンポーネントのルート要素に追加され、すでに指定されている既存のクラスとマージされます。

例えば、my-component という名前のコンポーネントがあり、次のようなテンプレートになっているとします:

<!-- 子コンポーネントのテンプレート -->
<p class="foo bar">Hi!</p>

そして、コンポーネントを使う際にクラスをいくつか追加します:

<!-- コンポーネントを使用する時点 -->
<my-component class="baz boo"></my-component>

レンダリングされる HTML は次のようになります:

<p class="foo bar baz boo">Hi</p>

クラスバインディングでも同様です:

<my-component :class="{ active: isActive }"></my-component>

isActive が真値のとき、レンダリングされる HTML は次のようになります:

<p class="foo bar active">Hi</p>

コンポーネントに複数のルート要素を持たせているときは、どの要素にクラスを渡すか指定する必要があります。これは、以下のように $attrs コンポーネントプロパティを使って行います:

<!-- $attrs を使った my-component のテンプレート -->
<p :class="$attrs.class">Hi!</p>
<span>This is a child component</span>
<my-component class="baz"></my-component>

レンダリング結果は次のようになります:

<p class="baz">Hi!</p>
<span>This is a child component</span>

コンポーネントの属性の継承については、フォールスルー属性のセクションで詳しく説明しています。

インラインスタイルのバインディング

オブジェクトへのバインディング

:style では次のような JavaScript のオブジェクト値へのバインディングがサポートされ、HTML 要素の style プロパティ に対応します:

const activeColor = ref('red')
const fontSize = ref(30)
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

CSS プロパティのキーにはキャメルケース (camelCase) が推奨されますが、:style では CSS の実際の書き方に対応するケバブケース (kebab-cased) のキーもサポートされています。例:

<div :style="{ 'font-size': fontSize + 'px' }"></div>

テンプレートをすっきりさせるため、多くの場合、次のようにスタイルオブジェクトを直接バインドするとよいでしょう:

const styleObject = reactive({
  color: 'red',
  fontSize: '13px'
})
<div :style="styleObject"></div>

スタイルへのオブジェクトのバインディングも、オブジェクトを返す算出プロパティと組み合わせて使用することが多くあります。

配列へのバインディング

:style は、複数のスタイルオブジェクトからなる配列にバインドすることができます。各オブジェクトはマージされ、同じ要素に適用されます:

<div :style="[baseStyles, overridingStyles]"></div>

自動プレフィックス

:style で ベンダープレフィックスを必要とする CSS プロパティを指定すると、Vue が適切なプレフィックスを自動的に追加します。Vue は、実行時にブラウザーでどのスタイルプロパティがサポートされているかをチェックして、適切なものを追加します。特定のプロパティがブラウザーでサポートされていない場合、Vue はさまざまなプレフィックスのバリエーションをテストし、サポートされているものを見つけようと試みます。

複数の値

style プロパティには、プレフィックス付きを含む複数の値を、配列で指定することができます。例:

<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

このように指定すると、配列に含まれる値のうち、ブラウザーでサポートされる最後の値のみがレンダリングに使われます。この例では、接頭辞なしのバージョンのフレックスボックスをサポートするブラウザーでは、display: flex がレンダリングに使われます。

[Vue入門]Computed Properties

基本的な例

テンプレート内に式を書けるのはとても便利ですが、非常に簡単な操作しかできません。テンプレート内に多くのロジックを詰め込むと、コードが肥大化し、メンテナンスが難しくなります。例えば、配列が入れ子(ネスト)になっているオブジェクトがあった場合:

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

そして、author がすでにいくつかの books を持っているかどうかによって、異なるメッセージを表示したいとします:

<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>

この時点で、テンプレートが少しごちゃごちゃしてきました。しばらく眺めて、やっとこれが author.books に依存した計算をしていることに気づくでしょう。さらに重要なことは、同じ計算をテンプレートの中で複数回使う場合、おそらく繰り返して使いたくはないでしょう。

上記の理由から、リアクティブなデータを含む複雑なロジックには算出プロパティを使用すべきです。以下は上記と同じ例をリファクタリングしたものです:

<script setup>
import { reactive, computed } from 'vue'

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

// 算出プロパティの参照
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})
</script>

<template>
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</template>

プレイグラウンドで試す

ここでは、publishedBooksMessage という算出プロパティを宣言しています。computed() 関数は getter 関数が渡されることを想定しており、返り値は 算出された ref となります。通常の ref と同様に、publishedBooksMessage.value で算出結果を参照することができます。また、算出結果はテンプレート内では自動的にアンラップされるため、テンプレート内では .value なしで参照することができます。

算出プロパティは、自動的にリアクティブな依存関係を追跡します。Vue は publishedBooksMessage の算出が author.books に依存することを知っているので、author.books が変わると publishedBooksMessage に依存する全てのバインディングを更新します。

算出プロパティ vs メソッド

こういった式を持つメソッドを呼び出すことで、同じ結果が実現できることに気付いたかもしれません:

<p>{{ calculateBooksMessage() }}</p>
// コンポーネント内
function calculateBooksMessage() {
  return author.books.length > 0 ? 'Yes' : 'No'
}

算出プロパティの代わりに、同じような関数をメソッドとして定義することもできます。最終的には、2 つのアプローチは完全に同じ結果になります。しかしながら、算出プロパティはリアクティブな依存関係にもとづきキャッシュされるという違いがあります。算出プロパティは、リアクティブな依存関係が更新されたときにだけ再評価されます。これはつまり、 author.books が変わらない限りは、publishedBooksMessage に何度アクセスしても、getter 関数を再び実行することなく、以前計算された結果を即時に返すということです。

Date.now() はリアクティブな依存ではないため、次の算出プロパティは二度と更新されないことを意味します:

const now = computed(() => Date.now())

対称的に、メソッド呼び出しは、再描画が起きると常に関数を実行します。

なぜキャッシングが必要なのでしょうか?巨大な配列をループしたり多くの計算を必要とする、コストの高い list という算出プロパティがあることを想像してみてください。list に依存する他の算出プロパティもあるかもしれません。その場合、キャッシングがなければ必要以上に list の getter を実行することになってしまいます。キャッシングしたくない場合は、代わりにメソッドを使いましょう。

書き込み可能な 算出関数

算出プロパティは、デフォルトでは getter 関数のみです。算出プロパティに新しい値を代入しようとすると、ランタイム警告が表示されます。まれに「書き込み可能な」算出プロパティが必要な場合があります。その場合は getter 関数と setter 関数の両方を提供することで、それを作成することができます:

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

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  // getter 関数
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter 関数
  set(newValue) {
    // 注意: ここでは、破壊的な代入構文を使用しています。
    ;[firstName.value, lastName.value] = newValue.split(' ')
  }
})
</script>

fullName = 'John Doe' を呼ぶと、setter 関数が呼び出され、firstName と lastName が適切に更新されます。

ベストプラクティス

getter 関数は副作用のないものでなければならない

算出プロパティにおける getter 関数は計算のみを行い、副作用がないようにすることが重要です。例えば、非同期リクエストや、DOM を変化させないようにしましょう!算出プロパティは他の値に基づいて計算する方法を宣言的に記述していると考えてください。その唯一の責任は、値を計算して返すことでなければなりません。このガイドの後半では、 ウォッチャー を使って、状態の変化に反応して副作用を実行する方法について説明します。

算出した値の変更を避ける

算出プロパティから返る値は、派生され状態です。一時的なスナップショットとして考えてください。ソースの状態が変わるたびに、新しいスナップショットが作成されます。スナップショットの値を変更することは意味がないため、計算された結果は読み取り専用として扱い、変更しないようにします。その代わり、新しい計算結果が必要な場合は、依存するソースの状態を更新します。