Vue.js入門 ―最速で作るシンプルなWebアプリケーション

第3回 Vue.jsでコンポーネント開発

この記事を読むのに必要な時間:およそ 5 分

はじめに

第3回では,Vue.jsのコンポーネントの基本的な作成の仕方と使用方法を紹介します。

Vue.jsは,UIをコンポーネント化する仕組みを持っています。HTML+CSS+JavaSciptで構築されたUIの再利用性が高まり,カプセル化されて開発で意識すべき範囲を限定できるようになります。今回の記事が,プロジェクトにVue.jsを導入する際のコンポーネント設計のイメージをつかむ助けになれば幸いです。

Vue.jsのコンポーネント指向

Vue.jsのコンポーネント指向について

Vue.jsのコンポーネントは,大まかにWeb ComponentCustom Elementsの仕様に沿って設計されています。コンポーネントは,定義したタグ名で親となるコンポーネントのHTML上に記述できます。

大規模なアプリケーションを作成する際は,コンポーネントをツリー状に構成してわかりやすく設計することが可能です。

基本的なcomponent

まず,基本的なVue.jsのコンポーネントの書き方を示します。

Vue.component('fruits-list-title', {
  template: '<h1>フルーツ一覧</h1>'
})

この例では,<h1>フルーツ一覧</h1>のHTML要素を含んだコンポーネントを,fruits-list-titleという名前で登録しています。登録したコンポーネントを別のコンポーネントから使用するには,以下のように親となるコンポーネントの中にHTMLタグを書きます。

<div id="fruits-list">
  <fruits-list-title></fruits-list-title>
</div>

このように構成した場合,実際にレンダリングされるHTML要素は,以下のようになります。

<div id="fruits-list">
  <h1>フルーツ一覧</h1>
</div>

上記の例ではグローバルのVueにコンポーネントを,Vue.component(tag, constructor)の形式で追加していましたが,ある特定のVueコンポーネントの中でのみ使えるように子コンポーネントを登録することもできます。Vue.extend()を使用してコンポーネントを作成した上で,親となるコンポーネントのcomponentsというoptionの中に以下のように登録してください。

var fruitsListChild = Vue.extend({
  template: '<h1>フルーツ一覧</h1>'
})

var fruitsListParent = Vue.extend({
  template: '<div>親コンポーネント<fruits-list-child></fruits-list-child></div>',
  components: {
    'fruits-list-child': fruitsListChild
  }
})

new Vue({
    el: "#fruits-list",
  components: {
    'fruits-list-parent': fruitsListParent
  }
})
<div id="fruits-list">
  <fruits-list-parent></fruits-list-parent>
</div>

jsfiddleで実行するとわかりますが,fruits-list-childコンポーネントはfruits-list-parentコンポーネントのスコープ内に定義されています。そのため,以下のHTMLファイルのようにfruits-list-childコンポーネントをfruits-list-parentの外側で指定しても,実行されません。

<div id="fruits-list">
  <fruits-list-child></fruits-list-child>
  <fruits-list-parent></fruits-list-parent>
</div>

コンポーネントのdataの扱い

各インスタンスごとに異なるdataオブジェクトを定義したいとき,dataオブジェクトは1つのコンポーネントの全てのインスタンスで共有されてしまいます。そのためVueコンポーネントの中のdataはオブジェクトを返す関数にします。

data: function(){
  return {
    fruits: [/* */]
  }
}

コンポーネント間の通信

親コンポーネントと子コンポーネントのデータのやりとりを解説します。

図1 コンポーネント間の通信図

図1 コンポーネント間の通信図

Vue.js公式ドキュメントから引用。

親コンポーネントが子コンポーネントへデータを渡す際には,propsを利用します。

例を見ていきましょう。以下のように,フルーツの名前をリストするコンポーネントを作成します。ここでは配列fruitsに入ったフルーツの名前fruit.nameをリストするテンプレートを含んだコンポーネントを,fruits-listという名前でVueインスタンスに登録しています。

Vue.component('fruits-list', {
  props: ['fruitsItems'],
  template: '<li>{{fruit.name}}</li>'
});

配列fruitsは親コンポーネントのdataで定義します。

new Vue({
  el: '#fruits-component',
  data: {
    fruits: [
      {name: '梨'},
      {name: 'イチゴ'}
    ]
  }
});

上記のように子コンポーネントのpropsオプションに変数名fruitsItemsを追加したうえで,HTML上の子コンポーネントのfruits-listタグの属性にfruits-itemsを記述します。すると,親コンポーネントから子コンポーネントに値を渡せます。

propsにキャメルケースでfruitsItemsと書いた場合,テンプレート側にはケバブケースでfruits-itemsと書きます。

<div id="ffruits-component">
  <ol>
    <fruits-list v-for="fruit in fruitsItems" fruits-items="fruits"></fruits-list>
  </ol>
</div>

レンダリングされるHTML要素は以下のようになります。

<div id="fruits-component">
  <ol>
    <li>梨</li>
    <li>イチゴ</li>
  </ol>
</div>

ここで,親のdataの変更を子に伝えるには,以下のようにv-bindディレクティブを使用すると良いでしょう。

<fruits-list v-for="fruit in fruitsItems" v-bind:fruits-items="fruits"></fruits-list>

このように記述すると,親コンポーネントでfruitsの値が更新されるたびに,子コンポーネントのpropsに書いたfruitsItemsが更新されます。なお,v-bindは省略できます。

<fruits-list v-for="fruit in fruitsItems" :fruits-items="fruits"></fruits-list>

子コンポーネントから親コンポーネントへの通信では,カスタムイベントを使用します。Vueインスタンスには,以下のようなイベントのインターフェイスが実装されています。

  • イベントのlisten: $on(eventName)
  • イベントのtrigger: $emit(eventName)

 $dispatchや$broadcastを紹介する記事を読んだことのある方もいるかもしれませんが,Vue.js 2.0では非推奨となりました。

こちらも例を見ていきましょう。以下のようにcounter-buttonコンポーネントが定義されているとします。ボタンを押すとこのコンポーネントのaddToCartメソッドが呼ばれ,その中でincrementというカスタムイベントが発行されます。親コンポーネント側では v-on:increment(increment)incrementイベントをlistenしているため,ボタンを押した時に親コンポーネントのincrementメソッドが呼ばれます。

var counterButton = Vue.component( {
  template: '<span>{{counter}}個<button v-on:click="addToCart">追加</button></span>',
  data: function () {
    return {
      counter: 0
    }
  },
  methods: {
    addToCart: function () {
      this.counter += 1
      this.$emit('increment')
    }
  },
});

new Vue({
  el: '#fruits-counter',
  components:
    'counter-button': counterButton
  },
  data: {
    total: 0,
    fruits: [
      {name: '梨'},
      {name: 'イチゴ'}
    ]
  },
  methods: {
    increment: function () {
      this.total += 1
    }
  }
});
<div id="fruits-counter">
  <div v-for="fruit in fruits">
    {{fruit.name}}: <counter-button v-on:increment="increment()"></counter-button>
  </div>
  <p>合計: {{total}}</p>
</div>

ここまでのサンプルコードは,こちらのjsfiddleで実行できます。

大規模なアプリケーションを作成していると,親と子の関係以外にもコンポーネントがさまざまな関係を持つことがあります。複雑なケースについては,ストアというオブジェクトに状態を持たせてそこで管理する方法(ストアパターン)が有効です。詳しくはこちらのドキュメントを参照してください。

著者プロフィール

野田陽平(のだようへい)

1985年静岡県生まれ。日本IBMソフトウェア開発研究所にて電子情報開示関連のソフトウェア開発に携わった後,株式会社プレイドに入社。Web接客ツールKARTEの開発を行っている。業務でもVue.jsを使用。アウトドア・フットサルが趣味。

Twitter:@positiveflat

コメント

コメントの記入