Vue.jsについて

Vue.jsとは?

JavaScriptのフレームワークで、Webアプリケーションのユーザーインターフェースを効率的に構築することを目的にしており、主にView部分を実装します。
他にも似たようなフレームワークがあり、以下のようなものがあげられます。

  • React
  • Angular

これらのフレームワークとの違いとしては学習が容易ということがあります。またドキュメントが充実しており、初心者には使いやすいとも言われてします。
Vue.js公式ドキュメント

JS FiddleでVue.jsを触ってみる

Vue.jsの起動

早速Vue.jsを触ってみましょう。簡単に触るだけならオンラインエディタを使うのがいいです。
今回はJS Fiddleを使用します。こちらにアクセスして、HTMLとJavaScriptに以下のコードを入力してください。(上に出てくる「Start with a boilerplate」のサジェストは使用しないので左の「Close」を押して閉じてください)

<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.22/dist/vue.js"></script>
new Vue({
  el: '#app',
  template: '<div>Hello World!</div>'
});

上のメニューバーにある「Run」を押すと、右下のペインに「Hello World!」と表示されていると思います。
実際に書いたものが以下のものとなります。もし動かない場合はこちらを参考にしてください。

ここから簡単に動作の解説をします。
まずHTMLの方ですが、<div id="app"></div>はVue.jsがマウントする場所を用意するために書かれています。id="app"という部分をキーとしてJS側で設定します。
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.22/dist/vue.js"></script>はVue.jsのライブラリファイルを読み込んでいます。これがないとVue.jsは動きません。
次にJSの方です。new VueでVue.jsを動かしますが、その時にマウント先になるのが先ほど指定したappというIDを振ったものになります。そこを指定するためにCSSセレクタの指定と同じように#appで指定します。マウント後に表示する内容をtemplateに設定します。今回はシンプルにHello World!という文字が表示されるようにしています。

次から簡単なサンプルをいくつか紹介したいと思います。なお、この際にES2015で記述しておりますので、見慣れない構文がある場合はES2015などで調べてみてください。

ES2015(ES6)について

JavaScriptは日々進化しています。毎年新機能が追加され、ES2015(ES6)から大きく変わりました。
例えば以下のような変更です。

詳細はこちらなどを参照してください。
これ以降も機能が追加されていますが、基本的にはES2015の内容を知っていれば大丈夫です。

ES2015の機能にあるテンプレート文字列を使用すると複数行の文字列が定義できるので、JS側でテンプレートを定義する際に書きやすくなります。

new Vue({
  el: '#app',
  template: `
    <div>
      <span>My Name is:</span>
      <span>Yamada Taro.</span>
    </div>
  `
});

変数を使用する

Vue.jsで変数を使ってみましょう。JS FiddleでJSペイン部分を以下に差し替えてみてください。

new Vue({
  el: '#app',
  data() {
    return {
      name: '山田太郎'
    };
  },
  template: '<div>Hello {{ $data.name }}!</div>'
});

これを実行すると以下のようなものが表示されると思います。また、name: '山田太郎'の部分を変えて実行すると表示される内容も変わると思います。

dataというプロパティメソッドで返ってきた値がVue.jsで使用できるデータになります。template内でこのdataにアクセスする場合は$data.~という風にアクセスします。

補足

data() { ... }はES2015にある省略表記になっており、以下と等価になります。

new Vue({
  el: '#app',
  data: function() {
    return {
      name: '山田太郎'
    };
  },
  template: '<div>Hello {{ $data.name }}!</div>'
});

$dataの省略

実は以下のように$dataを省略してもアクセスすることはできます。
しかし、Vue.jsには他にもpropsやcomputedという変数も存在しており、データが増えるとどこのデータなのかの判別が難しくなります。
そのためこのでは$dataと明記するようにしています。
他の記事では省略されていることが多いので混同しないように注意してください。

new Vue({
  el: '#app',
  data() {
    return {
      name: '山田太郎'
    };
  },
  // $data.nameと書かなくてもアクセスできる
  template: '<div>Hello {{ name }}!</div>'
});

プロパティの設定方法に指定はありませんが、以下の記事のようにするとみやすくなると思うので参考にしてみてください。
Vueのプロパティをわかりやすくする

リストを表示する

リストを表示する場合はv-forを使って以下のように書きます。

new Vue({
  el: '#app',
  data() {
    return {
      list: [1, 2, 3, 4]
    };
  },
  template: `
    <div>
      <ul>
        <template v-for="item in $data.list">
          <li>{{ item }}</li>
        </template>
      </ul>
    </div>
  `
});

templateを使ってv-forやv-ifを見やすくする

v-forは以下のようにliタグに直接書くこともできます。しかしこのように書くとパッと見liタグがループしているように見えない問題があります。
templateタグはVue.jsにあるタグで、他のタグをラップするだけのものなのでそこにv-forやv-ifを書くと、普段見慣れたfor () { ... }if () { ... }と同じブロック構造になるのでわかりやすくなると思います。

new Vue({
  el: '#app',
  data() {
    return {
      list: [1, 2, 3, 4]
    };
  },
  template: `
    <div>
      <ul>
        <li v-for="item in $data.list">{{ item }}</li>
      </ul>
    </div>
  `
});

vueで条件分岐、ループをみやすくする

双方向データバインディングを体験する

Vue.jsには双方向データバインディングという機能があるので、それを体験してみましょう。JS FiddleでJSペイン部分を以下に差し替えてみてください。

new Vue({
  el: '#app',
  data() {
    return {
      value: ''
    };
  },
  template: `
    <div>
      <p>双方向データバインディングの体験</p>
      <input v-model="$data.value" type="text"/>
      <p>{{ $data.value }}</p>
    </div>
  `
});

これで実行すると以下のようなものが表示されると思います。ここの入力フォームに文字を入力すると、入力した文字が下に表示されると思います。

コンポーネントを使用する

コンポーネントを使用する場合は以下のようにコンポーネントを用意して、componentsに設定します。
この時、コンポーネントに渡す値(props)の型を定義するためにVueTypesを使用しますので、HTMLに以下のコードを入れて読み込んでください。

<script src="https://unpkg.com/vue-types"></script>
const MyComponent = {
  props: {
    text: VueTypes.string.isRequired,  // $props.textは文字列型で必ず受け取る
    value: VueTypes.number.isRequired  // $props.valueは数値型で必ず受け取る
  },
  template: `
    <div>
      <div>propsで受け取ったデータ</div>
      <div>text: {{ $props.text }}</div>
      <div>value: {{ $props.value }}</div>
    </div>
  `
};

new Vue({
  el: '#app',
  components: {
    MyComponent
  },
  data() {
    return {
      value: 10
    };
  },
  template: `
    <div>
      <!-- textはただの文字列なのでHTML属性と同じように書いて渡す -->
      <!-- valueは数値でVue.jsのデータを渡したいので、先頭に:をつけて渡す -->
      <MyComponent
        text="テキスト"
        :value="$data.value"
      />
    </div>
  `
});

これを実行すると以下のようになります。

propsの定義

コンポーネントでは親からデータを受け取る変数を、propsで定義することができます。この時に受け取る型を指定しておくとブラウザのコンソール上で警告が出てバグを抑制できるのでVueTypesというライブラリを使用して型を指定しています。
詳細はこちらなどを参考にしてください。

const MyComponent = {
  props: {
    text: VueTypes.string.isRequired,  // $props.textは文字列型で必ず受け取る
    value: VueTypes.number.isRequired  // $props.valueは数値型で必ず受け取る
  },
  template: `
    <div>
      <div>propsで受け取ったデータ</div>
      <div>text: {{ $props.text }}</div>
      <div>value: {{ $props.value }}</div>
    </div>
  `
};

子コンポーネントにデータを渡す

コンポーネントにデータを渡す場合はHTMLタグと同じように属性に値を渡します。この時、Vue.jsの変数を渡したい場合は:value="$data.value"のように:をつけます。これがないときはただの文字列を渡すことになります。
ちなみに:v-bind:の略になります。

new Vue({
  el: '#app',
  components: {
    MyComponent
  },
  data() {
    return {
      value: 10
    };
  },
  template: `
    <div>
      <!-- textはただの文字列なのでHTML属性と同じように書いて渡す -->
      <!-- valueは数値でVue.jsのデータを渡したいので、先頭に:をつけて渡す -->
      <MyComponent
        text="テキスト"
        :value="$data.value"
      />
    </div>
  `
});

イベントを使う

子コンポーネントから親に情報を伝える時は$emitを使用して伝えます。
customSubmitという名前で伝える場合は、子コンポーネント側が$emit('customSubmit', ~)と書き、呼び出し側で@customSubmit="~"と書きますが、この時に名前を間違えないように注意してください。なお、@v-on:の略になります。
onclickやonsubmitなどのDOMのイベントも同じように@click, @submitでイベントを受け取ることができます。

const MyComponent = {
  data() {
    return {
      text: ''
    };
  },
  methods: {
    /**
     * 送信時
     * @param {event} event - DOMのイベント
     */
    onSubmit(event) {
      event.preventDefault();
      this.$emit('customSubmit', this.$data.text);
    }
  },
  template: `
    <form @submit="onSubmit">
      <input v-model="$data.text" type="text" />
      <button type="submit">送信</button>
    </form>
  `
};

new Vue({
  el: '#app',
  components: {
    MyComponent
  },
  data() {
    return {
      submittedText: ''
    };
  },
  methods: {
    /**
     * 送信された時
     * @param {string} text - テキスト
     */
    onSubmit(text) {
      this.$data.submittedText = text;
    }
  },
  template: `
    <div>
      <MyComponent
        @customSubmit="onSubmit"
      />
      <div>送信されたテキスト: {{ $data.submittedText }}</div>
    </div>
  `
});

JSDoc

メソッドについて概要や引数の内容などをコメントに残す書き方にJS Docというものがあります。これを書くことでどんな処理をするのか、どういった値を渡せばいいか分かりやすくなります。
変数などにも書けますが、コメントが増えてしまうので基本的には関数に対してJSDocで記述するといいと思います。

// JSDocの例
/**
 * 合計を求める
 * @param {Array<number>} list - 数値リスト
 * @returns {number} - 合計値
 */
function calcTotal(list) {
  let total = 0;
  for (let i = 0; i < list.length; i++) {
    total += list[i];
  }
  return total;
}

JSDocでJavaScriptのコメントを書こう

アニメーションする

Vue.jsはアニメーションに強くて、以下のようなアニメーションを簡単に実装することができます。

今回は量が多いのでdataとtemplate部分だけ抜粋します。

new Vue({
  // dataとtemplateのみ掲載
  data() {
    return {
      list: [1, 2, 3, 4, 5]
    }
  },
  template: `
    <div>
      <button @click="onShuffleClick">shuffle</button>
      <button @click="onAddButtonClick">add</button>
      <br>
      <ul class="list">
        <transition-group name="flip">
          <template v-for="item in $data.list">
            <li :key="item" class="item">
              <div class="item__text">{{ item }}</div>
              <div class="item__delete" @click="onDeleteClick(item)"></div>
            </li>
          </template>
        </transition-group>
      </ul>
    </div>
  `
});

アニメーションで重要なところはtransition-group:keyになります。
transition-groupで囲うことで、要素が現れる・消える時・移動する時にそれぞれにクラスが付与されます。移動する時だけ特殊なので後述します。
今回はnameにflipという名前を設定しているのでflip-enter, flip-leaveという名前が使われます。 実際は、アニメーションの初期設定、アニメーション中という2段構えのクラスが付与されるので、出現時にはflip-enter-active, flip-enter-toというクラス名が1フレーム差でそれぞれが付与されます。
あとはこれに対応したCSSアニメーションを設定すればOKです。CSSの設定例はこちらになります。今回はSCSSを使ってCSSの設定をしています。

.flip {
  // 要素が入るときのアニメーション
  &-enter {
    &-active {
      opacity: 0;
      transform: translate3d(0, -30px, 0);
    }
    &-to {
      opacity: 1;
      transform: translate3d(0, 0, 0);
    }
  }

  // 要素が消える時のアニメーション
  &-leave {
    &-active {
      position: absolute;
    }
    &-to {
      opacity: 0;
      transform: translate3d(0, -30px, 0);
    }
  }
}

SCSS(sass)

SCSSはCSSの機能に追加して以下のようなことができます。
今回のアニメーションの設定のような時はSCSSを使うと書きやすくなります。

  • 変数が使用できる
  • ネストができる
  • mixinが使える

Sass(SCSS)でCSSコーディングを効率化・メリットと使い方を知る

なお、SCSSはsassと対になっており、sassは括弧を使わずにインデントでブロックを表現したり、記述が簡略化されていますが、基本的にやれることは一緒です。

説明を後回しにしていた要素が移動するときの話ですが、これも同じようにクラスが付与されます。ただし、移動の時はflip-moveとクラス名は一つだけ付与されます。移動はスムーズに移動するアニメーションをしたい思うので、基本的にはtransition: transform 0.5sとか付与するだけでいいです。

.flip {
  // 要素が動くときのアニメーション
  &-move {
    transition: transform 0.5s;
  }
}

最後に:keyですが、これは簡単にいうと要素のIDになります。要素を削除したり追加したりしたときに、元の位置からどこへ移動したかを判定するためにIDをキチンと設定することで、正しい要素にアニメーション用のクラス名が付与されます。これが例えば配列の順番とかを設定すると、要素が変わった時に元の要素とのIDが変わってしまうため正しいアニメーションができなくなります。
そのため、keyにする値は必ずユニークな値にする必要があります。(今回は登録される数値が絶対に被らないように最大値+1にしています)

<transition-group name="flip">
  <template v-for="(item, index) in $data.list">
    <!-- 配列のindexをkeyにした場合、例えば3番目が削除されても全てのものが前に詰まってしまうので一番後ろが消えたものだと勘違いしてしまう -->
    <li :key="index" class="item">
      <div class="item__text">{{ item }}</div>
      <div class="item__delete" @click="onDeleteClick(item)"></div>
    </li>
  </template>
</transition-group>

この設定ができれば後はリストの抜き差しをするだけになります。この辺の操作はコメントにも記してあるので解説は省略します。

lodash

JavaScriptの計算系の処理をユーティリティにまとめられたライブラリです。標準のJavaScriptの機能だけでは足りないところを補ってくれるため、必要に応じて使うと実装が楽になります。今回はlodashのshuffleを使用しています。

lodashの機能一覧(一部)

  • shuffle (配列をシャッフルする)
  • find (配列内の検索。IEでネイティブ対応していないため、その代わり)
  • uniq (配列の重複要素を取り除く)
  • throttle (スクロールイベントなど連続でメソッドが呼び出される時に実行を間引いてくれる)
  • cloneDeep (オブジェクトの完全コピー)
  • round (小数点以下の値で四捨五入する時に使える。ネイティブでは整数しか対応していない)
  • sum (配列の合計)
  • clamp (最大値と最小値を超えないように値を調整してくれる)
  • times (0からインクリメントされた配列を生成する)

lodashのドキュメント

このアニメーションについてはこちらの記事でも解説しているので、アニメーションに興味がある場合は合わせて確認してみてください。

TODOリストを作ってみる

今までの内容を使ってTODOリストを作ってみましょう。サンプルは以下になりますので、難しければ参考にしてみて下さい。

Vue.jsのtemplateの定義方法

先ほどまではマウント後に表示する内容をtemplateキーに記述していましたが、Vue.jsには様々な設定方法があります。これを知らないと混乱してしまうので、しっかり理解しましょう。

templateキーに記述する

今まで書いてきた方法です。HTMLに記述していた<div id="app"></div><div>Hello World!</div>に差し変わるイメージです。

new Vue({
  el: '#app',
  template: '<div>Hello World!</div>'
});

HTML側にtemplateを記述する

templateキーに設定しなかった場合はマウント先の要素をそのまま使います。以下のように書いても先ほどのものと同じ動きをします。
今のままだとなんの意味もありませんが、#appの中にはVue.js特有の構文を書いて、反映させることができます。
この書き方はVue.jsなどのフレームワークの特徴の一つであるコンポーネント化が出来なくなるため、あまりオススメできません。しかしながら、本当に小さなコードを書く分には問題ないため、公式ドキュメントではこちらの書き方になっています。

<div id="app">
  <div>Hello World!</div>
</div>
new Vue({
  el: '#app'
});

SFCでtemplateを定義する

ウェブページを作るにあたって必要になるものがHTML, CSS, JavaScriptですが、これらがバラバラにあるとどのコードがどこと関係しているかが分かりにくくなります。Vue.jsにはSFC(Single File Component)という仕組みがあり、1つのファイルでHTML, CSS, JavaScriptを書くことができます。こちらはローカルで開発するときにしか使えないため、その時に詳細を説明します。

<template>
  <div>
    Hello World!
  <div>
</template>

<script>
export default {
};
</script>

<style lang="scss" scoped>
//
</style>