js
vue.js
コンポーネント

Vue.jsのコンポーネント入門

規模が大きくなってきたらコンポーネント化しよう

Vue.jsを一番シンプルに書くとこんな感じ。

main.js
<div id="app">
  {{ message }}
</div>

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

これでもいいけど、規模が大きくなってくるとなかなかメンテナンスが大変になってくる。
またheaderなど、複数画面で共通して使いたい場合もでてくる。

そんなときに便利なのがコンポーネント。

コンポーネントを登録する

コンポーネントを使用する場合、まずは登録が必要となる。

グローバルに使いたい場合

html側の #app #app2 内で my-component のカスタムタグが使えるようになる。

main.js
// Vue.component(tagName, option)でグローバルにコンポーネントを登録
// これで全インスタンスで使えるようになる
// 必ずしもカスタムタグ名を「W3Cルール」にする必要はない

Vue.component('my-component', {
  template: '<div>グローバルで登録したコンポーネントです。</div>'
})

new Vue({
  el: '#app'
})

new Vue({
  el: '#app2'
})

スコープを切って使いたい場合

複数のインスタンスが存在する場合、スコープを切って特定のインスタンスでだけ使えるようにもできる。

main.js
// スコープを切ってコンポーネントを登録したいときはまず変数におく
// コンポーネントを登録したapp2でのみコンポーネントが読み込まれる

const Scoped = {
    template: '<div>スコープを切ったコンポーネント</div>'
}

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

new Vue({
  el: '#app2',
  components: { //Scopedが使える
    'scoped-component': Scoped,
  }
})

コンポーネントにデータを持たせる

コンポーネントの場合、データを関数として返す必要がある。

main.js
// コンポーネントにデータを渡すときは関数にしてreturnしないといけない
const DataComp = {
    template: '<div>私の名前は{{name}}です</div>',
    data: ()=> {
      return {
        name: "花子"
      }
    },
}

new Vue({
    el: '#app2',
    components: {
      'data-component': DataComp,
    }
})

親子間でのデータの受け渡し

コンポーネントは基本的に単一では使用せず、親であるインスタンスやコンポーネントと一緒に使うことになる。

メンテナンスの手間や拡張の可能性を考慮すると、それぞれのコードはできるだけ独立した形にしてデータを受け渡すような実装が理想。
親子間でのデータ受け渡しは直接参照できないため、以下のようなものを利用する。

親から子:props

親のデータを参照する場合、子コンポーネント内の props オプションでプロパティを宣言する。
この val は親のデータが入ってくるハコを作っているだけなので、名前はなんでも良い。

main.js
// 子コンポーネント(データが入ってくるハコを用意)
const PropsComp = {
    props: ['val'],
    template: '<div>朝起きたら{{val}}といいましょう。</div>',
}

// 親コンポーネント
new Vue({
    el: '#app2',
    data: {
      message: "good morning",
      hoge: "hoge"
    },
    components: {
      'props-component': PropsComp
    }
})

親コンポーネントのmessageをプロパティに渡せば(hogeでもいいけど) 「朝起きたらgood morningといいましょう。」 と表示されるようになる。

index.html
<div id="app2">
    <props-component :val="message"></props-component>
</div>

子から親:emit

子コンポーネントのボタンのクリック数を親コンポーネントで参照する場合を考える。

Screen Shot 2018-01-17 at 18.03.52.png

main.js
// 子コンポーネント
Vue.component('button-counter', {
    template: '<button v-on:click="increChild">{{ counter }}</button>',
    data:  ()=> {
        return {
            counter: 0
        }
    },
    methods: {
        increChild: function () {
            this.counter += 1
            this.$emit('increment')
        }
    },
})

// 親コンポーネント
new Vue({
    el: '#counter-event-example',
    data: {
        total: 0
    },
    methods: {
        increParent: function () {
            this.total += 1
        }
    }
})
index.html
    <div id="counter-event-example">
      <p>クリック数合計:{{ total }}</p>
      <button-counter v-on:increment="increParent"></button-counter>
    </div>

イベントの伝達順序

  1. 子コンポーネントの数値が加算される: increChild
  2. increment イベントが発火される: $emit('increment')
  3. 親コンポーネントの数値が加算される: increParent

キャメルケース?ケバブケース?どっち?

カスタムタグ名 プロパティ名を書くときは「W3Cルール」(全て小文字で、ハイフンが含まれている ≒ ケバブケース)ではなく、キャメルケースでもOK。

main.js
const camelKebab = {
    props: ['myMessage'], //プロパティ名
    template: '<div>This is {{myMessage}}.</div>',
}

new Vue({
    el: '#app2',
    data: {
      message: "good morning",
    },
    components: {
      'camelKebab': camelKebab //カスタムタグ名
    }
})

ただし、htmlの属性は case-insensitive(大文字小文字を区別しない) ため 実際に使用する場合は ケバブケース する必要がある。

index.html
<div id="app2">
  <!--これはだめ-->
  <!--<camelKebab></camelKebab>-->

  <camel-kebab my-message="message"></camel-kebab>
</div>
キャメル ケバブ
Vue側
html側 X

A. キャメルケースを使ってもいいが、html側では必ずケバブケースにする