# コンポーネントの基本
# 基本例
Vue コンポーネントの例を次に示します:
// Vue アプリケーションを作成します
const app = Vue.createApp({})
// グローバルな button-counter というコンポーネントを定義します
app.component('button-counter', {
data() {
return {
count: 0
}
},
template: `
<button @click="count++">
You clicked me {{ count }} times.
</button>`
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
INFO
ここでは単純な例を示していますが, 典型的な Vue アプリケーションでは文字列テンプレートではなく単一ファイルコンポーネントを使用します。 詳しくはこちらで解説しています。
コンポーネントは名前付きの再利用可能なインスタンスです。この例の場合は<button-counter>
です。このコンポーネントをルートインスタンスの中でカスタム要素として使用することができます。
<div id="components-demo">
<button-counter></button-counter>
</div>
2
3
app.mount('#components-demo')
コンポーネントは再利用可能なインスタンスなので、data
、 computed
、 watch
、 methods
、そしてライフサイクルフックのようなルートインスタンスと同様のオプションが利用可能です。唯一の例外は el
のようなルート固有のオプションです。
# コンポーネントの再利用
コンポーネントは必要なだけ何度でも再利用できます:
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
2
3
4
5
ボタンをクリックすると、それぞれが独自の count
を保持することに注意してください。 これはコンポーネントを使用する度に新しいコンポーネントのインスタンスが作成されるためです。
# コンポーネントの構成
一般的にアプリケーションはネストされたコンポーネントのツリーに編成されます:
例えば、 ヘッダー、サイドバー、コンテンツエリアなどのコンポーネントがあり、それぞれには一般的にナビゲーションリンクやブログ投稿などのコンポーネントが含まれています。
これらのコンポーネントをテンプレートで使用するためには、 Vue がそれらを認識できるように登録する必要があります。 コンポーネントの登録にはグローバルとローカルの 2 種類があります。これまでは、アプリケーションの component
メソッドを利用してグローバルに登録してきただけです:
const app = Vue.createApp({})
app.component('my-component-name', {
// ... オプション ...
})
2
3
4
5
グローバルに登録されたコンポーネントはその後作成された app
インスタンスのテンプレートで使用することができます。さらに、ルートインスタンスのコンポーネントツリーの全てのサブコンポーネント内でも使用することが出来ます。
とりあえずコンポーネント登録についてはこれで以上ですが、このページを読み終えて十分に理解できたら、後から戻ってきてコンポーネント登録の完全なガイドを読むことをお勧めします。
# プロパティを用いた子コンポーネントへのデータの受け渡し
先ほど、 ブログ投稿用のコンポーネントの作成について触れました。問題は、 表示する特定の投稿のタイトルや内容のようなデータを作成したコンポーネントに渡せなければそのコンポーネントは役に立たないということです。プロパティはここで役立ちます。
プロパティはコンポーネントに登録できるカスタム属性です。値がプロパティ属性に渡されると、そのコンポーネントインスタンスのプロパティになります。先ほどのブログ投稿用のコンポーネントにタイトルを渡すためには、props
オプションを用いてこのコンポーネントが受け取るプロパティのリストの中に含めることができます:
const app = Vue.createApp({})
app.component('blog-post', {
props: ['title'],
template: `<h4>{{ title }}</h4>`
})
app.mount('#blog-post-demo')
2
3
4
5
6
7
8
コンポーネントは必要に応じて多くのプロパティを持つことができ、デフォルトでは任意のプロパティに任意の値を渡すことができます。上記のテンプレートでは、data
と同様に、コンポーネントインスタンスでこの値にアクセスできることが分かります。
プロパティが登録されたら、 次のようにカスタム属性としてデータをプロパティに渡すことができます:
<div id="blog-post-demo" class="demo">
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>
</div>
2
3
4
5
しかしながら、一般的なアプリケーションではおそらく data
に投稿の配列を持っています:
const App = {
data() {
return {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]
}
}
}
const app = Vue.createApp(App)
app.component('blog-post', {
props: ['title'],
template: `<h4>{{ title }}</h4>`
})
app.mount('#blog-posts-demo')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
そしてコンポーネントをそれぞれ描画します:
<div id="blog-posts-demo">
<blog-post
v-for="post in posts"
:key="post.id"
:title="post.title"
></blog-post>
</div>
2
3
4
5
6
7
上記では、 v-bind
を用いて動的にプロパティを渡すことができると分かります。これは描画する内容が事前に分からない場合に特に便利です。
とりあえずプロパティについてはこれで以上ですが、 このページを読み終えて十分に理解できたら、後から戻ってきてプロパティの完全なガイドを読むことをお勧めします。
# 子コンポーネントのイベントを購読する
<blog-post>
コンポーネントを開発する中で、いくつかの機能で親コンポーネントとの通信が必要になるかもしれません。例えば、残りの部分の大きさはそのままで、ブログ記事の文字の文字を拡大するアクセシビリティ機能を実装することを決めるかもしれません。
親コンポーネントでは、postFontSize
データプロパティを追加することでこの機能をサポートすることができます:
const App = {
data() {
return {
posts: [
/* ... */
],
postFontSize: 1
}
}
}
2
3
4
5
6
7
8
9
10
すべてのブログ投稿のフォントサイズを制御するためにテンプレート内で使用できます:
<div id="blog-posts-events-demo">
<div v-bind:style="{ fontSize: postFontSize + 'em' }">
<blog-post v-for="post in posts" :key="post.id" :title="title"></blog-post>
</div>
</div>
2
3
4
5
それでは、すべての投稿の内容の前にテキストを拡大するボタンを追加します:
app.component('blog-post', {
props: ['title'],
template: `
<div class="blog-post">
<h4>{{ title }}</h4>
<button>
Enlarge text
</button>
</div>
`
})
2
3
4
5
6
7
8
9
10
11
問題は、このボタンが何もしないことです:
<button>
Enlarge text
</button>
2
3
ボタンをクリックすると、全ての投稿のテキストを拡大する必要があることを親に伝える必要があります。親は、ネイティブ DOM イベントでの場合と同様に、 v-on
や @
を用いて子コンポーネントのインスタンスでのイベントを購読することができます:
<blog-post ... @enlarge-text="postFontSize += 0.1"></blog-post>
そして子コンポーネントはビルトインの $emit
メソッドにイベントの名前を渡して呼び出すことで、イベントを送出することができます:
<button @click="$emit('enlarge-text')">
Enlarge text
</button>
2
3
親コンポーネントは v-on:enlarge-text="postFontSize += 0.1"
リスナーによって、このイベントを受け取り postFontSize
を更新することができます。
コンポーネントの emits
オプションにより排出されたイベントをリストアップすることができます。
app.component('blog-post', {
props: ['title'],
emits: ['enlarge-text']
})
2
3
4
これにより、コンポーネントが排出する全てのイベントをチェックし、オプションでそれらを検証することができます。
# イベントと値を送出する
イベントを特定の値と一緒に送出すると便利な場合があります。例えば、テキストをどれだけ大きく表示するかを <blog-post>
コンポーネントの責務とさせたいかもしれません。そのような場合、 $emit
の第二引数を使ってこの値を渡すことができます:
<button @click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
2
3
そして親でこのイベントを購読すると、 $event
を用いて排出されたイベントの値にアクセスすることができます:
<blog-post ... @enlarge-text="postFontSize += $event"></blog-post>
または、イベントハンドラがメソッドの場合:
<blog-post ... @enlarge-text="onEnlargeText"></blog-post>
値はそのメソッドの第一引数として渡されます:
methods: {
onEnlargeText(enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
2
3
4
5
# コンポーネントで v-model
を使う
カスタムイベントは v-model
で動作するカスタム入力を作成することもできます。このことを覚えておいてください:
<input v-model="searchText" />
これは以下と同じことです:
<input :value="searchText" @input="searchText = $event.target.value" />
コンポーネントで使用する場合、 v-model
は代わりにこれを行います:
<custom-input
:model-value="searchText"
@update:model-value="searchText = $event"
></custom-input>
2
3
4
WARNING
ここでは in-DOM テンプレートを使用しているため、 model-value
をケバブケースで表記していることに注意してください。ケバブケースの属性とキャメルケースの属性に関してはDOM テンプレートの構文解析の注意点の章で詳しく解説されています。
これが実際に機能するためには、テンプレート内の <input>
は以下でなければなりません:
value
属性をmodelValue
プロパティにバインドするinput
では、update:modelValue
イベントを新しい値と共に送出する
以下のようになります:
app.component('custom-input', {
props: ['modelValue'],
template: `
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
`
})
2
3
4
5
6
7
8
9
これで v-model
はこのコンポーネントで完璧に動作します:
<custom-input v-model="searchText"></custom-input>
カスタムコンポーネント内で v-model
を使うもう一つの方法は computed
プロパティを利用してゲッターとセッターを定義することです。
以下の例では、computed プロパティを用いて custom-input
コンポーネントをリファクタリングします。
注意して欲しいのは、 get
メソッドは modelValue
属性を返し、バインディングに使用しているプロパティがどれであるかに関わらず、 set
メソッドはそのプロパティに対応する $emit
を送出しなければならないということです。
app.component('custom-input', {
props: ['modelValue'],
template: `
<input v-model="value">
`,
computed: {
value: {
get() {
return this.modelValue
},
set(value) {
this.$emit('update:modelValue', value)
}
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
とりあえずカスタムコンポーネントイベントについてはこれで以上ですが、このページを読み終えて十分に理解できたら、後から戻ってきてカスタムイベントの完全なガイドを読むことをお勧めします。
# スロットによるコンテンツ配信
HTML 要素のように、コンポーネントに要素を渡すことができると便利なことがよくあります。例えば以下の通り:
<alert-box>
Something bad happened.
</alert-box>
2
3
これは以下のように描画されるでしょう。:
幸いにも、この作業は Vue のカスタム <slot>
要素により非常に簡単になります:
app.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
2
3
4
5
6
7
8
上で見た通り、ただ渡したいところにスロットを追加するだけです。それだけです。終わりです!
とりあえずスロットについてはこれで以上ですが、このページを読み終えて十分に理解できたら、後から戻ってきてスロットの完全なガイドを読むことをお勧めします。
# 動的なコンポーネント
タブ付きのインターフェースのように、コンポーネント間を動的に切り替えると便利なことがあります:
上記は Vue の <component>
属性に特別な属性である is
を持たせることで実現しています:
<!-- コンポーネントは currentTabComponent に変更があったときに変更されます -->
<component :is="currentTabComponent"></component>
2
上記の例では、 currentTabComponent
は以下のいずれかを含むことができます:
- 登録されたコンポーネントの名前、または
- コンポーネントのオプションオブジェクト
完全なコードを試すには この例、登録された名前ではなくコンポーネントのオプションオブジェクトをバインドしている例はこちらのバージョンを参照してください。
この属性は通常の HTML 要素で使用することができますが、それらはコンポーネントとして扱われ、すべての属性は DOM 属性としてバインドされることを覚えておいてください。 value
のようないくつかのプロパティが期待通りに動作するためには、 .prop
修飾子を用いてバインドする必要があります。
とりあえず動的なコンポーネントについてはこれで以上ですが、このページを読み終えて十分に理解できたら、後から戻ってきて動的 & 非同期コンポーネントの完全なガイドを読むことをお勧めします。
# DOM テンプレートパース時の警告
<ul>
、 <ol>
、 <table>
、 <select>
のようないくつかの HTML 属性にはその内側でどの要素が現れるかに制限があり、<li>
、 <tr>
、 <option>
のようないくつかの属性は他の特定の要素の中にしか現れません。
このような制限を持つ属性を含むコンポーネントを使用すると問題が発生することがあります。例えば:
<table>
<blog-post-row></blog-post-row>
</table>
2
3
このカスタムコンポート <blog-post-row>
は無効なコンテンツとして摘み出され、最終的に描画された内容にエラーが発生します。幸い、これを回避するために v-is
という特殊なディレクティブを使用することができます:
<table>
<tr v-is="'blog-post-row'"></tr>
</table>
2
3
WARNING
v-is
の値は JavaScript の文字列リテラルである必要があります:
<!-- 間違い、何も出力されません-->
<tr v-is="blog-post-row"></tr>
<!-- 正解 -->
<tr v-is="'blog-post-row'"></tr>
2
3
4
5
また、 HTML の属性名は大文字小文字を区別しないので、ブラウザは全ての大文字を小文字として解釈します。つまり、 in-DOM テンプレートを使用している場合、キャメルケースのプロパティ名やイベントハンドラのパラメータはそれと同等のケバブケース(ハイフンで区切られた記法)を使用する必要があります:
// JavaScript ではキャメルケース
app.component('blog-post', {
props: ['postTitle'],
template: `
<h3>{{ postTitle }}</h3>
`
})
2
3
4
5
6
7
8
<!-- HTML ではケバブケース -->
<blog-post post-title="hello!"></blog-post>
2
3
これらの制限は次のソースのいずれかの文字列テンプレートを使用している場合 適用されない ことに気をつけてください:
- 文字列テンプレート (例:
template: '...'
) - 単一ファイル (
.vue
) コンポーネント <script type="text/x-template">
とりあえず DOM テンプレートパース時の警告についてはこれで以上です。そして実は、Vue の 本質 の最後となります。おめでとうございます! まだまだ学ぶべきことはありますが、まずは一休みして自分で Vue を実際に使って楽しいものを作ってみることをお勧めします。
理解したばかりの知識に慣れてきたら、サイドバーのコンポーネントの詳細セクションの他のページと同様に、動的 & 非同期コンポーネントの完全なガイドを読むために戻ってくることをお勧めします。