もがき系プログラマの日常

もがき系エンジニアの勉強したこと、日常のこと、気になっている技術、備忘録などを紹介するブログです。

Vue.js入門 基礎から実践アプリケーション開発までを読んだ vol.5

はじめに

こんにちは。

Kojirockの1人アドベントカレンダー Advent Calendar 2018の5日目の記事です。

以下の本を読んで勉強できたことを記載していきます。

今回は第5章です。

Vue.js入門 基礎から実践アプリケーション開発まで

Vue.js入門 基礎から実践アプリケーション開発まで

5章 Vue.jsの高度な機能

目次

5.1 トランジションアニメーション

5.2 スロット

5.3 カスタムディレクティブ

5.4 描画関数

5.5 ミックスイン

5.1 トランジションアニメーション

Vue.jsでのアニメーション方法が記載されています。

要素の表示・非表示をトリガーにして、CSS3、Web AnimationAPI、各アニメーションライブラリとの連携を行い、トランジションアニメーションを簡単に実装できるということでした。

アニメーション関連は自分は全く知識なかったので、正直この部分に関しては、内容理解しづらかったです。

<transition> タグで囲ったものが、アニメーションの対象となります。

以下の条件の際に、トランジションのクラスが要素に追加されるようです。

  1. v-ifの条件が変わった時
  2. v-showの条件が変わった時
  3. 動的コンポーネントのis属性値が変わった時

追加されるクラスは以下のとおりです。

クラス名 追加されるタイミング 削除されるタイミング
v-enter 要素が挿入される前 挿入のアニメーション開始時
v-enter-to 挿入のアニメーションの開始時 挿入のアニメーション終了後
v-enter-active 要素の挿入前 挿入のアニメーション終了時
v-leave 削除のアニメーションの開始前 削除のアニメーション開始時
v-leave-to 削除のアニメーションの開始時 削除のアニメーション終了後
v-leave-active 削除のアニメーションの開始前 削除のアニメーション終了後

実際に本書で紹介されているコードが以下になります。

<!DOCTYPE html>
<html>
<head>
    <title>Page Title</title>
    <script src="https://unpkg.com/vue@2.5.17"></script>
</head>
<body>
    <div id="app">
        <button @click="isShown = !isShown">表示切り替え</button>
        <transition>
          <p v-show="isShown">Hello World</p>
        </transition>
    </div>
    
    <script>
        new Vue({
            el: '#app',
            data: function() {
              return {
                isShown: false
              }
            }
        })
    </script>

    <style>
      .v-enter-active, .v-leave-active {
        transition: opacity 500ms ease-out;
      }

      .v-enter {
        opacity: 0;
      }

      .v-enter-to {
        opacity: 1;
      }

      .v-leave {
        opacity: 1;
      }

      .v-leave-to {
        opacity: 0;
      }
    </style>
</body>
</html>

f:id:kojirooooocks:20181204224559g:plain

f:id:kojirooooocks:20181204224631g:plain

↑のように、アニメーション時に先ほど紹介したクラスが着脱されてますね。

公式に乗っていた、以下の画像がとてもわかり易かったです。

f:id:kojirooooocks:20181204224704p:plain

その他にも、実践的なアニメーションの方法(javascriptへのフックなど)が紹介されています。

ただ、自分のフィールド的に、「まだ必要ないかな」と判断したので、こちらは読み飛ばしました。

5.2 スロット

以前の記事にも登場していたスロットの話です。

本書ではモーダルを表示するコンポーネントが例として取り上げられています。

モーダル内の内容がテキスト1文だけであれば、プロパティで渡すだけでもよいかもしれませんが、内容が複雑多岐に渡る場合、プロパティで全部渡すのは辛いです。

モーダル内の要素が動的に切り替えられることで、そのモーダルコンポーネントは再利用が効く優秀なコンポーネントになります。

スロットには2種類のタイプが存在します。

名前なしスロット

<!DOCTYPE html>
<html>
<head>
    <title>Page Title</title>
    <script src="https://unpkg.com/vue@2.5.17"></script>
</head>
<body>
  <div id="app">
    <ul>
      <li-item>味噌ラーメン</li-item>
      <li-item>塩ラーメン</li-item>
      <li-item>醤油ラーメン</li-item>
      <li-item>とんこつラーメン</li-item>
      <li-item>餃子</li-item>
      <li-item></li-item>
    </ul>
  </div>
    
  <script>
    new Vue({
      el: '#app',
      components: {
        LiItem: {
          template: `
            <li><slot>売り切れ</slot></li>
          `
        }
      }
    })
  </script>
</body>
</html>

f:id:kojirooooocks:20181204224728p:plain

templateの中にある<slot>タグで囲った中が、コンテンツを差し込まれる場所となっています。

各ラーメンと餃子に関しては、コンテンツ(今回の場合味噌ラーメンなどのテキスト)が指定されているため、HTMLでは、 <li>味噌ラーメン</li>になります。

コンテンツが指定されていない最後の

  • 要素では、で指定している売り切れが差し込まれます。

    イメージ的に、親からデータが渡されなかった場合のデフォルト要素みたいな感じでしょうか。

    名前付きスロット

    前述の名前なしスロットは単一スロットといって、一つのコンテンツしか挿入させることが出来ませんでした。

    それ以外に、コンテンツを挿入したい場合は、この名前付きスロットを使います。

    使い方はそこまで変わらず、<slot> タグにname属性で名前をつけて、呼び出す際もそのnameを指定する形です。

    そうすることで狙ったスロットに挿入できます。

    <!DOCTYPE html>
    <html>
    <head>
        <title>Page Title</title>
        <script src="https://unpkg.com/vue@2.5.17"></script>
    </head>
    <body>
      <div id="app">
        <contents-body>
          <ul slot="pickup_item">
              <li>11111</li>
              <li>22222</li>
              <li>33333</li>
          </ul>
    
          <ul slot="discount_item">
            <li>現在商品はありません。</li>
          </ul>
    
          <ul slot="normal_item">
            <li>AAAAA</li>
            <li>BBBBB</li>
            <li>CCCCC</li>
            <li>DDDDD</li>
            <li>EEEEE</li>
            <li>FFFFF</li>
            <li>GGGGG</li>
            <li>HHHHH</li>
            <li>IIIII</li>
            <li>KKKKK</li>
            <li>LLLLL</li>
          </ul>
    
          <ul slot="soldout_item">
              <li>JJJJJ</li>
          </ul>
        </contents-body>
      </div>
        
      <script>
        new Vue({
          el: '#app',
          components: {
            ContentsBody: {
              template: `
                <div>
                  <h3>ピックアップ商品</h3>
                  <slot name="pickup_item">
                    <ul>
                      <li>AAAAA</li>
                      <li>BBBBB</li>
                      <li>CCCCC</li>
                    </ul>
                  </slot>
    
                  <h3>特別割引商品</h3>
                  <slot name="discount_item">
                    <ul>
                      <li>DDDDD</li>
                      <li>EEEEE</li>
                      <li>FFFFF</li>
                    </ul>
                  </slot>
    
                  <h3>通常商品</h3>
                  <slot name="normal_item">
                    <ul>
                      <li>GGGGG</li>
                      <li>HHHHH</li>
                      <li>IIIII</li>
                    </ul>
                  </slot>
    
                  <h3>売り切れ</h3>
                    <slot name="soldout_item">
                    <ul>
                      <li>JJJJJ</li>
                      <li>KKKKK</li>
                      <li>LLLLL</li>
                    </ul>
                  </slot>
                </div>
              `
            }
          }
        })
      </script>
    </body>
    </html>
    

    f:id:kojirooooocks:20181204224748p:plain

    こんな感じで複雑な差し替えも可能になりました。

    これはかなり使えそうです。

    5.3 カスタムディレクティブ

    v-ifやv-modelといった標準搭載のディレクティブでは扱いきれない複雑なものにたいして、独自のディレクティブを作成できる仕組みです。

    アプリケーション全体で使用できるグローバルディレクトリと、特定のコンポーネントでのみ使用できるカスタムディレクトリがあるようですが、基本的にディレクティブはコンポーネントに依存しないものなので、グローバルディレクトリで作成するのが良さそうです。

    こちらは、本書で紹介されていた例がとてもわかり易かったです。

    <!DOCTYPE html>
    <html>
    <head>
        <title>Page Title</title>
        <script src="https://unpkg.com/vue@2.5.17"></script>
    </head>
    <body>
      <div id="app">
        <img v-fallback-image src="./logo.png">
      </div>
        
      <script>
        Vue.directive('fallback-image', {
          bind: function(el) {
            el.addEventListener('error', () => {
              el.src = 'https://dummyimage.com/400x400/000/ffffff.png&text=no+image'
            })
          }
        })
    
        new Vue({
          el: '#app',
        })
      </script>
    </body>
    </html>
    

    f:id:kojirooooocks:20181204224803p:plain

    画像が読み込めない場合指定の画像をsrcにセットして、代わりに表示するようなロジックです。

    例題では、bindというフックを使用しましたが、その他別のフックもあります。

    • bind
    • inserted
    • update
    • componentUpdated
    • unbind

    本書ではそれぞれの説明とupdateフックによるサンプルコードが記載されてます。

    また、先程のコードの機能拡張版のコードも紹介されています。

    この拡張版のコードはより実践的なコードなので、すぐ使用できそうなものになっていると思いました。

    5.4 描画関数

    これも最初の方に少しだけ触ったものです。

    templateで指定せず、render()を使用するという方式です。

    以前は、どちらも使えます的な紹介でしたが、今回は実際に、描画関数を使用しないと書きづらい例が載せられていました。

    以下が本書で紹介されていた例です。

    template使用時

    <!DOCTYPE html>
    <html>
    <head>
        <title>Page Title</title>
        <script src="https://unpkg.com/vue@2.5.17"></script>
    </head>
    <body>
      <div id="app">
        <my-button href="https://vuejs.org">anchor</my-button>
        <my-button tag="span">span</my-button>
        <my-button>button</my-button>
      </div>
        
      <script>
        new Vue({
          el: '#app',
          components: {
            MyButton: {
              props: [
                'href', 'tag'
              ],
              template: `
                <a v-if="(!tag && href) || tag === 'a'" :href="href || '#'">
                  <slot></slot>
                </a>
                <span v-else-if="tag === 'span'">
                  <slot></slot>
                </span>
                <button v-else>
                  <slot></slot>
                </button>
              `
            }
          }
        })
      </script>
    </body>
    </html>
    

    render()使用時

    <!DOCTYPE html>
    <html>
    <head>
        <title>Page Title</title>
        <script src="https://unpkg.com/vue@2.5.17"></script>
    </head>
    <body>
      <div id="app">
        <my-button href="https://vuejs.org">anchor</my-button>
        <my-button tag="span">span</my-button>
        <my-button>button</my-button>
      </div>
        
      <script>
        new Vue({
          el: '#app',
          components: {
            MyButton: {
              props: [
                'href', 'tag'
              ],
              render: function(h) {
                const tag = this.tag || (this.href ? 'a' : 'button')
                return h(tag, {
                  attrs: {
                    href: this.href || '#'
                  }
                }, this.$slots.default)
              }
            }
          }
        })
      </script>
    </body>
    </html>
    

    tenplate自体にv-ifなどの条件分岐ディレクティブが取り外されたので、確かに見通しは良さそうです。

    ちょっと実際に使ってみないとわからないですが、どの程度使うかは怪しいところです(すっかり忘れてtemplateだけ使っちゃいそう...)

    5.5 ミックスイン

    機能は一緒だけど、見た目が違うような場合にミックスインの機能が使えます。

    <!DOCTYPE html>
    <html>
    <head>
        <title>Page Title</title>
        <script src="https://unpkg.com/vue@2.5.17"></script>
    </head>
    <body>
      <div id="app">
        <red-ranger></red-ranger>
        <blue-ranger></blue-ranger>
        <yellow-ranger></yellow-ranger>
        <green-ranger></green-ranger>
        <pink-ranger></pink-ranger>
      </div>
        
      <script>
        const Attack = {
          methods: {
            attack: function() {
              alert(`必殺!! ${this.special}ーーーー!!`)
            }
          }
        }
    
        const RedRanger = {
          data: function() {
            return {
              special: 'レッドスパーク',
            }
          },
          mixins: [Attack],
          template: '<div><span>アカレンジャー参上!</span><button @click="attack">必殺技</button></div>'
        }
        const BlueRanger = {
          data: function() {
            return {
              special: 'ブルーチェリー',
            }
          },
          mixins: [Attack],
          template: '<div><span>アオレンジャー参上!</span><button @click="attack">必殺技</button></div>'
        }
        const YellowRanger = {
          mixins: [Attack],
          methods: {
            attack: function() {
              alert('カレーが切れて力が出ないー...')
            }
          },
          template: '<div><span>キレンジャー参上!</span><button @click="attack">必殺技</button></div>'
        }
        const GreenRanger = {
          data: function() {
            return {
              special: 'ミドメラン',
            }
          },
          mixins: [Attack],
          template: '<div><span>ミドレンジャー参上!</span><button @click="attack">必殺技</button></div>'
        }
        const PinkRanger = {
          mixins: [Attack],
          methods: {
            attack: function() {
              alert('モモレンジャーは敵に捕まっている!!')
            }
          },
          template: '<div><span>モモレンジャー...?</span><button @click="attack">必殺技</button></div>'
        }
    
        new Vue({
          el: '#app',
          components: {
            RedRanger,
            BlueRanger,
            YellowRanger,
            GreenRanger,
            PinkRanger
          }
        })
      </script>
    </body>
    </html>
    

    ちょっと使い方あってるか微妙でしたが、、、

    mixins で、使用したいミックスインを追加します。

    今回の例だと、attackというメソッドを持つミックスインをそれぞれのコンポーネントに追加してます。

    モモレンジャーキレンジャーにもミックスインを追加してますが、attack()というメソッドを追加して、ミックスインを上書きしています。

    終わりに

    かなり実践的な章でした。

    とくにスロットとミックスインはすごく使えそうです。

    とりあえず、今回はこれで終わり。