masakariblog

日々のマサカリと戦い、強くなる過程を書くブログ

Vue.jsをもくもくするその18 コンポーネント編 その4

component_move.html

<html>
    <head>
        <meta charset="utf-8"/>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
        <link rel="stylesheet" href="index.css">
    </head>
    <body>
        <div id="app">
            <component v-bind:is="currentAdBanner" />
        </div>
        <div id="app-tab">
            <div id="container">
                <ul>
                    <li v-for="tab in tabNames">
                        <a href="#" v-on:click.prepend="onclick(tab)">{{ tabs[tab] }}</a>
                    </li>
                </ul>
                <keep-alive>
                    <component v-bind:is="currentAdTab"></component>
                </keep-alive>
            </div>
        </div>
        <div id="model-app">
            <message-input v-model="hello"></message-input>
            <p>どんなあいさつ? {{ hello }}</p>
        </div>
        <div id="model-app2">
            <message-input v-model="hello"></message-input>
            <p>どんなあいさつ? {{ hello }}</p>
        </div>
        <div id="model-app3">
            <message-input v-model="hello"></message-input>
            <p>どんなあいさつ? {{ hello }}</p>
        </div>
        <div id="model-app4">
            <message-input v-bind:value.sync="hello"></message-input>
            <p>どんなあいさつ? {{ hello }}</p>
        </div>
        <script src="https://jp.vuejs.org/js/vue.js"></script>
        <script src="component_move.js"></script>
        <script src="component_tab.js"></script>
        <script src="component_model.js"></script>
        <script src="component_model_2.js"></script>
        <script src="component_model_3.js"></script>
        <script src="component_model_4.js"></script>
    </body>
</html>

動的なコンポーネント

Vue.jsの標準搭載の組み込みコンポーネントについて解説します。

要素  コンポーネントが要素化されたものです。つまり、コンポーネント要素の中に、コンポーネントA、コンポーネントB、コンポーネントCを入れて、表示を替えたりすることが可能となります。

サンプルコード

component_move.jsおよびhtmlで解説をします。

まず、コンポーネント自体の入れ物となる要素を用意します。currentAdBannerの値で、表示されるコンポーネントが切り替わる実装となります。

次に、dataオプションにcomponentsプロパティを定義します。

ここで注意すべきなのが、componentsオプションではないことです。

プロパティとして定義し、ここから表示すべきコンポーネントを取得します。

あとは、算出プロパティを用いて、コンポーネント名を5秒おきに、変更できるようにします。

keep-alive

この<keep-alive>属性をつけることで、コンポーネントを変更したとしても、破棄されたコンポーネントを内部で残しておくことができます。

component_move.js

Vue.component('ad-banner-real-estate', {
  template: `<div class="ad-banner"> <h3>不動産借りてみませんか?</h3>
             <p>年利5%の不労所得</p></div>`
});
Vue.component('ad-banner-cram-school', {
  template: `<div class="ad-banner"> <h3>東大合格者数1.5人突破</h3>
             <p>名公義塾!</p></div>`
});
Vue.component('ad-banner-game', {
  template: `<div class="ad-banner"> <h3>無人島で豊かな一人暮らしライフ</h3>
             <p>あつまれ一人暮らしの海</p></div>`
});
new Vue({
  el: '#app',
  created: function() {
    let that = this;
    this.interval = setInterval(function(){
      that.current = (that.current + 1) % that.components.length;
    }, 10000);
  },
  beforeDestroy: function() {
    clearInterval(this.interval);
  },
  computed: {
    currentAdBanner: function() {
      return 'ad-banner-' + this.components[this.current];
    }
  },
  data: {
    current: 0,
    components: ['real-estate','game','cram-school']
  }
});

component_tab.js

Vue.component('ad-tab-real-estate', {
  template: `<div class="ad-tab"> <h3>不動産借りてみませんか?</h3>
             <p>年利5%の不労所得</p>
             <label>お名前: <input type="text" v-model="name"></label>
             <input type="submit" value="登録"/>
             </div>`,
  data: function() {
    return {
      name: ''
    }
  }
});
Vue.component('ad-tab-cram-school', {
  template: `<div class="ad-tab"> <h3>東大合格者数1.5人突破</h3>
             <p>抵抗義塾!</p>
             <label>お名前: <input type="text" v-model="name"></label>
             <input type="submit" value="登録"/>
             </div>`,
  data: function() {
    return {
      name: ''
    }
  }
});
Vue.component('ad-tab-game', {
  template: `<div class="ad-tab"> <h3>無人島で豊かな一人暮らしライフ</h3>
             <p>あつまれ一人暮らしの海</p>
             <label>お名前: <input type="text" v-model="name"></label>
             <input type="submit" value="登録"/>
             </div>`,
  data: function() {
    return {
      name: ''
    }
  }
});
new Vue({
  el: '#app-tab',
  methods: {
    onclick: function(tab) {
      this.current = tab;
    }
  },
  computed: {
    tabNames: function() {
      return Object.keys(this.tabs);
    },
    currentAdTab: function() {
      return 'ad-tab-' + this.current;
    }
  },
  data: {
    current: 'real-estate',
    tabs: {
      'real-estate': '不動産',
      'game': 'ゲーム',
      'cram-school': '学習塾'
    }
  }
});

コンポーネントにおけるv-model

下記のコードに示してあるのは、親コンポーネントからv-modelを定義して、子コンポーネントのpropsで値を受け取り、v-bind,v-onで紐付ける方法です。

コンポーネントでもv-modelを使えば、紐付けられるのにお気づきでしょうか? (私も一番最初に、親も子もv-modelで紐付ければいいのではと思いました。)

しかし、プロパティは、v-modelに渡せません。なぜならpropsは親コンポーネントからデータを受け取るためのものです。

component_model_2.js

Vue.component('message-input', {
  props: [ 'value' ],
  template: `<label>
            名前: 
            <input type="text" v-model="internalValue" />
            </label>`,
  computed: {
    internalValue: {
      get() {
        return this.value;
      },
      set(newValue) {
        if (this.value !== newValue) {
          this.$emit('input', newValue);
        }
      }
    }
  }
});

new Vue({
  el: '#model-app2',
  data: {
    hello: ''
  }
});

modelオプションについて

コンポーネントにおいて、v-modelで紐付くプロパティやイベントは、子コンポーネントのmodelオプションで変更できる。

component_model_3.js

Vue.component('message-input', {
  props: [ 'name' ],
  model: {
    prop: 'name',
    event: 'change'
  },
  template: `<label>
            名前: 
            <input
             type="text" v-bind:value="name"
             v-on:input="$emit('change', $event.target.value)" />
             </label>`
});

new Vue({
  el: '#model-app3',
  data: {
    hello: ''
  }
});

.sync修飾子について

コンポーネントで、<message-input v-bind:value.sync="hello"></message-input>と.sync修飾子つけた場合もv-modelと同様の挙動で動きます。

component_model_4.js

Vue.component('message-input', {
  props: [ 'value' ],
  template: `<label>
            名前: 
            <input
             type="text" v-bind:value="value"
             v-on:input="$emit('update:value', $event.target.value)" />
             </label>`
});

new Vue({
  el: '#model-app4',
  data: {
    hello: ''
  }
});