【Vue.js】コンポーネント(Commponent)入門【特徴やメリットなど】

JavaScript

Vue.jsでも取り入れられているコンポーネントっていったいどんなものなんだろう?その考え方や便利な使い方を知りたいなあ。

そんな疑問にお答えします。

この記事では、前回書ききれなかったVue.jsの便利な仕組みの一つ「コンポーネント」について解説していきます。

その中でも、今回は基本のコンポーネントについて詳しく解説していきます。単一ファイル型コンポーネントに関してはまた別の記事で解説する予定です。

Vue.jsの基本的な部分から知りたいという方は下記の記事で詳しく解説していますので、是非ご覧ください。

この記事では基本的に上記の内容をある程度理解した方向けに書いていきます。ターゲットとしては、主に以下に当てはまる方となります。

  • Webにおけるコンポーネントというものが何なのか知りたい
  • Vue.jsコンポーネントの基本やメリットを知りたい
  • Vue.jsコンポーネントの便利な使い方を知りたい

この記事を読み終える頃には、あなたもコンポーネントの基本的な使い方や、どういうときに使うのがいいのか等が分かるようになっていると思います。

それでは始めていきましょう。

そもそもコンポーネント(Commponent)とは?

ここ数年で、Webコンポーネント指向という考え方が表に出てくるようになりました。コンポーネントとは、ざっくり言うと「それぞれ機能を持った、使い回せるパーツ(塊)」です。

この考え方はBootstrapを始めとしたCSSフレームワークでもすでに多く取り入れられており、今回のVue.jsやReactでも基本の仕組みとして取り入れられたことで、今後さらに当たり前になるであろう考え方です。

コンポーネントを使うことのメリット

例えばひとつのブログサイトには、ヘッダーや記事リスト、サイドバー等いくつかのページで共通して使うパーツが存在します。これらのパーツを「コンポーネント」として分割して管理することで、いろいろと便利に制作を進めていくことが出来ます。

コンポーネントには下記のような特徴やメリットがあります。これらを適切に使っていくことで効率的にWebサイト制作を行うことが可能です。

デザインや機能をまとめて一つの塊として扱える

コンポーネントの大きな特徴のひとつとして、デザインや機能をまとめて一つの塊として扱えるという点があります。つまり、

  • DOM(HTML)
  • 振る舞い・機能(JavaScript)
  • デザイン(CSS)※Vue.jsでは単一ファイル型コンポーネントのみ

の3つを一つのパーツとして扱う事ができます。これにより、よりパーツ単位での切り分けが可能になり、サイト全体の見通しがよくなります。

このCSSやJavaScriptも含めてパーツ化できるという点が、従来のHTMLだけを切り分ける方法と大きく異なる点です。

Bootstrapでも使われている手法

先程も書いたとおり、このコンポーネント指向はBootstrapを始めとしたCSSフレームワークにも取り入れられています。

例えば「Bootstrapのドロップダウン」は決められたclassを追加するだけでデザインだけでなくドロップダウンの機能も持たせる事ができます。つまり、Bootstrapのドロップダウンはコンポーネントとしてパーツ化されているわけです。

BootstrapではHTMLは自分で記述します(デザインと機能のみ内部で定義されている)。ですがあらかじめ書き方は決められているため、人によって使い方がズレることはありません

最終的に誰が使っても同じ使い方ができるのであれば、それはコンポーネント化が出来ていると言えます。

このように、DOMだけでなくデザインや機能までを一つの塊として使えるようになったことでページ内部の見通しがよくなり、特にチーム単位での開発がしやすくなりました。

何度でも使い回すことができる

コンポーネント化することによる最大のメリットです。後に紹介するメリットも、この使い回しによる副次的なメリットであることが多いです。

コンポーネントは一度定義してしまえば何度でも使い回す事が可能です。これにより、ヘッダー等の共通パーツを一つのコンポーネント、つまり一度の記述で実装することが出来ます。

この後Vue.jsでのコンポーネントについて紹介していきますが、Vue.jsではコンポーネントをファイルとして管理することで、どこにでも呼び出して同じように使うことができます。

パーツの一元管理により、メンテナンスが簡単

一つのファイルを使い回している関係上、メンテナンスが容易になります。

デザインの変更や機能の改善、修正などがある場合でも一つを更新すればすべてに反映されます。データベースもそうですが、一元管理することによりメンテナンスが容易になり、更新忘れなどの人的ミスが仕組み上起こらない開発が可能になります。

開発速度の短縮につながる

Web制作に限ったことではありませんが、最初から最後までスムーズに進むということはあまりありません。メンテナンス性が高く、途中での仕様変更や調整に強いコンポーネントはそういった際に真価を発揮します。

チーム開発や大規模開発時にも強く、人数や規模が大きくなるほど開発時間の短縮に繋がりやすいです。

パターンを作るなどのカスタマイズが簡単にできる

Bootstrapのボタン」では、classを付け替えることで様々な色や形のボタンを作ることができます。

これと同じようにVue.jsのコンポーネントでも、パラメータを与えてあげることで様々なパターンを作成することが可能です。それにより、似たような表記を何度もすることなく効率的にパーツを作成することができます。

このように、コンポーネントとしてまとめることで様々なメリットがあります。最初にコンポーネントを作る手間や全体の構造を把握する力は必要になりますが、効率的で柔軟な開発が可能になるので覚えておいて損はないでしょう。

Vue.jsのコンポーネントとは?

それではVue.jsでは実際にどのようにコンポーネントを作り、利用していくのかをこれから解説していきます。

Vue.jsには2種類のコンポーネントがある

Vue.jsには2種類のコンポーネントの書き方があります。基本のVueコンポーネントと、単一ファイル型コンポーネントです。

この2つの書き方にはそれぞれ特徴がありますが、大きな違いは「Vue CLIによるコンパイルが必要かどうか」と「CSSがコンポーネントに含まれるかどうか」です。

Vue CLIとはVue.jsプロジェクトをより効率的に開発することができるようになる公式の専用ツールです。こちらについてはまた別の記事で詳しく触れていく予定です。
Vue CLI公式ページ

まず、一つ目の基本的なVue コンポーネントには下記のような特徴があります。

  • Vue CLIによるコンパイルが不要でいつでも利用できる
  • HTMLとJavaScriptを塊としてコンポーネントとする
  • HTMLは文字列として書くため、シンタックスハイライトは効かない

細かな点は後ほど実践の中で解説しますが、ざっくり言うと「機能が制限されている代わりに手軽に使えるコンポーネント」という感じです。

2つ目の単一ファイル型コンポーネントには下記のような特徴があります。

  • 「.vue」というファイルで一つのコンポーネントを扱う
  • Vue CLIによるコンパイルが必要
  • HTML、CSS、JavaScriptがコンポーネントに含まれる
  • それぞれシンタックスハイライトが利用可能

こちらはCSSも含めてコンポーネント内で利用できます。つまり他のCSSと完全に切り分ける事ができ、全体のCSSの見通しがよくなります。こちらは「Vue CLIが必須な代わりに全ての機能を持ったコンポーネント」というイメージですね。

だいたい2種類のコンポーネントの位置づけが分かっていただけたでしょうか。今回は基本のコンポーネントについて、実際のコードを見ながら解説していきたいと思います。

Vue コンポーネント(基本)

基礎構文

まず、前回の記事でも紹介したVue自体の基礎構文を書きましょう。

var app = new Vue({
  el: '#app',
})

コンポーネントはこのVue基礎構文の外側に書きます。以下のような感じです。

Vue.component('my-button', {
  template: '<button>ボタンです</button>'
})

var app = new Vue({
  el: '#app',
})

これだけで、コンポーネント「my-button」が登録されました。第一引数がタグの名前になります。HTMLに書くことでボタンが表示されます。

<div id="app">
  <my-button></my-button>
</div>

忘れがちですがVue.jsの大元ファイルをこちらのCDNなどで読み込んでおきましょう。これがないとそもそもVue.jsが動かないので注意です。

Vue.component第二引数内のtemplate: 'ボタンです'が実際に描画されるHTMLになります。クォーテーションで囲まれた文字列ですのでシンタックスハイライトが効かず書きづらいのがちょっぴり難点です。

ともあれ、これで最も基本的なコンポーネントは完成しました。

コンポーネントの一番外側は必ず一つのタグで括る

今回の例では「button」タグ一つだったので問題ありませんでしたが、コンポーネントの一番外側は必ず一つのタグで括らなければいけないというルールがあります。

例えば以下の書き方ではエラーが出ます。

Vue.component('my-block', {
  template: `
    <h3>見出しです</h3>
    <p>本文です</p>
  `
})

エラーの理由は、コンポーネントの一番外側のタグが「h3」と「p」で2つあるからです。こういう書き方をしたいときは以下のように一つのタグで括る必要があります。

Vue.component('my-block', {
  template: `
    <div>
      <h3>見出しです</h3>
      <p>本文です</p>
    </div>
  `
})

これで問題なく表示されるようになります。特に難しい点ではありませんが、意外と忘れがちなので、気をつけましょう。

コンポーネントをファイルで分けてみよう

基本的なコンポーネントは完成しましたが、このコンポーネントはVue.jsの基礎構文と同じファイルに書いているため、このままでは使いまわしが難しそうです。

そんなときはこのコンポーネント部分を普通に「my_button.js」などで切り分ければOKです。説明するほどでは無いかもしれませんが、下記のような感じです。

Vue.component('my-button', {
  template: '<button>ボタンです</button>'
})
<body>
<div id="app">
  <my-button></my-button>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script src="my_button.js"></script>
<script>
var app = new Vue({
  el: '#app',
})
</script>
</body>

これでmy-buttonを使いたい場所で読み込めば使い回せます。

ちなみにこれは単一ファイル型コンポーネントとは言いません。あくまでも「.vue」の拡張子で作られた専用のコンポーネント用ファイルのみを単一ファイル型コンポーネントと呼びます。

slot: 親からコンポーネントのテキストを設定してみよう

Vue.jsのコンポーネント機能の中で、まずは簡単に使える「slot」について解説していきます。

「slot」は、親のタグ内に書いたテキストをそのままコンポーネント内に表示できるという仕組みです。

例えばボタンに表示するテキストを親から指定する時などに使います。具体的には下記のような感じです。

<div id="app">
  <my-button>いち</my-button>
  <my-button>に</my-button>
  <my-button>さん</my-button>
</div>
Vue.component('my-button', {
  template: `
    <button>
      <slot></slot>
    </button>
  `
})

このように、親側のタグ内にテキストを入力し、コンポーネント側に<slot></slot>というタグを追加します。

そうすると、実際の表示の際に「slot」が親のタグ内のテキストに置き換わります。

この例では「いち」「に」「さん」と書かれた3つのボタンが出来上がります。便利な仕組みなので是非覚えておきましょう。

data: コンポーネントにデータをもたせてみよう

これまでのコンポーネントにはHTMLの要素しか含まれていませんでしたが、ここにJavaScriptを追加することができます。上手く使えば様々な機能を持たせることができます。

今回は簡単に、ボタンを押した回数を表示するボタンを作ってみます。このような感じで書きます。

Vue.component('my-button', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button @click="count++">{{ count }} 回押しました</button>'
})

先程は「template」だけだったプロパティに「data」が追加されています。ここはいつものVue.jsのdataと同じように使うことができます。ただ書き方が少し変わっていますので注意しましょう。

また、template内部にも「@click」と「{{ count }}」があるのが分かると思います。このようにVue.jsのディレクティブも使えます。

CodePenで作成したものを載せておくので、試しにクリックしてみてください。ちゃんと動いているのが分かると思います。

See the Pen Vue button :data by はとむぎ (@hatomugi_s) on CodePen.

複数配置した場合の処理について

ちなみに、他のタグと同じように当然何度も配置することができます。その際それぞれのコンポーネントは独立して動き、data等は個別に判定されます。

例えば下記の場合は、それぞれのボタンを押した回数は個別に保存されます。

<div id="app">
  <my-button></my-button>
  <my-button></my-button>
  <my-button></my-button>
</div>

こちらもCodePenを載せておきます。それぞれの押した回数が独立して表示されているのが分かると思います。

See the Pen Vue button :data :3times by はとむぎ (@hatomugi_s) on CodePen.

例えばこれを共有したい場合は、このコンポーネントを呼び出す側などの別の場所に保存しておく必要があります。

そんな時のためにVue.jsでは、コンポーネントと呼び出し側で値のやり取りをすることが可能です。次の章で解説する「props」と「emit」を使うことで実装することができます。

props: 親からコンポーネントに値を渡してみよう

では、実際にコンポーネントと値を受け渡ししていきましょう。

まずは親(呼び出し元)からコンポーネントに値を渡す方法です。「my-button-2」というタグに親からtitle属性を渡してみます。

<div id="app">
  <my-button-2 title="ボタンのタイトルです"></my-button-2>
</div>
Vue.component('my-button-2', {
  props: ['title'],
  template: '<button>{{ title }}</button>'
})

コンポーネント内に「props」という項目が追加され、値として「title」が登録されているのが分かると思います。

これにより、HTMLタグの属性「title」を取得してコンポーネント内で使うことが出来るようになります。これが親→コンポーネントへの値の受け渡しの基本です。

複数の属性を渡す

props: ['title']の値部分は配列になっているため、複数の属性を渡すことができます。例えば以下のようなことも可能です。

<div id="app">
  <my-button-2 title="Hatoblog" href="https://hatoblog.net/"></my-button-2>
</div>
Vue.component('my-button-2', {
  props: ['title', 'href'],
  template: '<button><a :href="href">{{ title }}</a></button>'
})

「title」と「href」属性を渡し、「href」はaタグの同属性に入れています。これで、ボタンのタイトルとジャンプ先をコンポーネントの外側から渡すことが出来るようになりました。

メインのVueオブジェクトのdataを使って値を渡す

先程はHTMLに属性をベタ打ちしましたが、それではせっかく仕組み化した意味がありません。今度はメインのVueオブジェクトのデータを使ってコンポーネントに値を渡してみましょう。実際の使い方はこちらの方が多いと思います。

<div id="app">
  <my-button-2 v-for="item in items" :title="item.title" :href="item.href"></my-button-2>
</div>
Vue.component('my-button-2', {
  props: ['title', 'href'],
  template: '<button><a :href="href">{{ title }}</a></button>'
})

var app = new Vue({
  el: '#app',
  data: {
    items: [
      { title: 'Hatoblog', href: 'https://hatoblog.net/' },
      { title: 'Google', href: 'https://www.google.co.jp/' },
      { title: 'Twitter', href: 'https://twitter.com/' },
    ]
  }
})

メインのVueオブジェクトのdataに「items」という配列を追加し、それを使ってコンポーネントを作成しました。例の如くCodePenを用意しましたので、実際の動きを確認してみてください。

See the Pen Vue button :props by はとむぎ (@hatomugi_s) on CodePen.

このように、親からコンポーネントに値を渡すことで、使い方の幅が大きく広がりました。

$emit: コンポーネントから値を受け取ろう

次はコンポーネントから親(呼び出し元)に値を渡す方法です。ここまで来ればほとんどコンポーネントの基本をマスターしたといっても過言ではありません。

Vue.jsでは「v-on:(または@)」を使ってコンポーネント内の任意のイベントを取得することが出来ます。

ここでは、前章のボタンを押した回数を共有する機能を付けてみましょう。下記のように書いていきます。

<div id="app">
  <my-button @count-plus="count++" :num="count"></my-button>
  <my-button @count-plus="count++" :num="count"></my-button>
  <my-button @count-plus="count++" :num="count"></my-button>
</div>
Vue.component('my-button', {
  props: ['num'],
  template: `<button @click="$emit('count-plus')">{{ num }} 回押しました</button>`
})

var app = new Vue({
  el: '#app',
  data: {
    count: 0
  }
})

少し複雑なので一つずつ解説していきます。

前提として今回は親のVueオブジェクトに「count: 0」を持っており、これをすべてのボタンで共有して増やしていきます。

まず、HTML側の:num="count"は先程の親→コンポーネントへの値の受け渡しです。コンポーネント内では、「num」という属性名で親のカウント数を受け取っています。

次に、Vue.jsのコンポーネント内「template」に@click="$emit('count-plus')"という初めての記述があります。この「$emit」で「count-plus」という自作イベントが発火したことを親に伝えることが出来ます。今回はボタンをクリックすることでこのイベントを発火しています。

そして、この「count-plus」イベントをHTML側の@count-plus="count++"で受け取っています。イベントの発火を検知したら自分(親)の「count」を+1しているというわけです。

このように、「コンポーネント側でイベントの発火を親に伝え、親はそれを検知したらそれに対応する動作を行う」というのがコンポーネント→親の値の受け渡しの流れになります。

例の如くCodePenで動きを確認してみましょう。どのボタンを押してもカウントが共有されているのが分かると思います。

See the Pen Vue button :$emit :data :3times by はとむぎ (@hatomugi_s) on CodePen.

同じ動きをメソッドを使って実装

今回は「count++」という形で、その場で動作させていますが、当然メソッドを指定することも出来ます。数行の動作にも対応できるので、慣れてきたらこちらを使うといいでしょう。

親、コンポーネント、どちらもメソッドを使って実装することが可能です。

<div id="app">
  <my-button @count-plus="countPlus" :num="count"></my-button>
  <my-button @count-plus="countPlus" :num="count"></my-button>
  <my-button @count-plus="countPlus" :num="count"></my-button>
</div>
Vue.component('my-button', {
  props: ['num'],
  methods: {
    countReturn: function(){
      $emit('count-plus');
    }
  },
  template: `<button @click="countReturn">{{ num }} 回押しました</button>`
})

var app = new Vue({
  el: '#app',
  data: {
    count: 0
  },
  methods: {
    countPlus: function(){
      this.count++;
    }
  }
})

親に渡す値を指定することもできる

また、今回はカウントを1ずつ足していましたが、この親に渡す値を動的に変更することも可能です。

Vue.component('my-button', {
  props: ['num'],
  methods: {
    countReturn: function(){
      $emit('count-plus', 2);
    }
  },
  template: `<button @click="countReturn">{{ num }} 回押しました</button>`
})

Vue.jsコンポーネント側は、$emit('count-plus', 2)に第二引数を指定することで、この値を親に渡せます

<div id="app">
  <my-button @count-plus="countPlus($event)" :num="count"></my-button>
  <my-button @count-plus="countPlus($event)" :num="count"></my-button>
  <my-button @count-plus="countPlus($event)" :num="count"></my-button>
</div>
var app = new Vue({
  el: '#app',
  data: {
    count: 0
  },
  methods: {
    countPlus: function(plus_num){
      this.count += plus_num;
    }
  }
})

コンポーネントから渡された値は、HTML側の@count-plus="countPlus($event)"のように「$event」で取得することが出来ます。

あとはメインのVueオブジェクトのメソッドで、その値を「count」に足してあげればOKです。

このように、コンポーネントから親に好きな値を返すことが出来ます。

まとめ

ということで、Vue.jsのコンポーネントの基礎と、基本のコンポーネントの使い方について解説してきました。前回に比べて内容が複雑になっており、一度では理解しきれないかも知れません。

すべてを一度に使いこなすことは難しいと思いますので、焦らずに試しながら、少しずつ慣れていきましょう。

実際はもっと細かなポイントがありますが、今回はVueを始めとしたWebプログラミング初心者を対象としているため割愛します。詳しく知りたい方、余裕のある方は公式ドキュメントをご覧ください。
コンポーネントの基本 – Vue.js

今後、単一ファイル型コンポーネントやVue CLIについての記事も書いていく予定です。皆さんもどんどんVue.jsを使っていきましょう。