kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Vue.js の基礎を学ぶために LearnCode.academy の学習動画を活用したら最高だった

LearnCode.academy とは?

www.youtube.com

LearnCode.academy という YouTube チャンネルがあり,Vue.js や React + Redux などを解説した,無料の学習動画が公開されている.今回受講したのは「Vue Tutorial」で,全9回となっている.合計しても「約1時間程度」なので,スキマ時間を活用して気軽に学ぶこともできる.「Vue.js は気になるけどまだ試したことがない」という人にオススメ!

  • Vue Tutorial #1 - Vue JS Tutorial for Beginners #1 setting up an app
  • Vue Tutorial #2 - Vue.js filters and computed data
  • Vue Tutorial #3 - Vue.js directives and event listeners
  • Vue Tutorial #4 - Break up your app into Vue Components
  • Vue Tutorial #5 - AJAX data and Lifecycle Methods
  • Vue Tutorial #6 - Full REST Example
  • Vue Tutorial #7 - Scaling Vue with Single File Components
  • Vue Tutorial #8 - Vue Router
  • Vue Tutorial #9 - Scaling Vue.js Data with Stores

なお,動画の中で実装されているコードは全て GitHub に公開されているので,動画を見ながら写経しても良いし,最初に Vue.js アプリケーションを動かしてから動画を見ることもできる.

github.com

Vue Tutorial #1

まず,雛形となる index.html に,マウントポイントとなる div を作成した.

<div id="app"></div>

次に,Vue インスタンスを作成する.

  • el ... マウントポイントとなる要素のセレクター
  • data ... 処理するデータオブジェクト
  • template ... テンプレート

Vue.js のテンプレートは Mustache 構文になっているので {{john.name}} のように書けば,データオブジェクトの値を反映することができる.

const app = new Vue({
  el: "#app",
  data: {
    bobby: {
      name: "Bobby Boone",
      age: 25
    },
    john: {
      name: "John Boby",
      age: 35,
    }
  },
  template: `
    <div>
      <h2>Name: {{john.name}}</h2>
      <h2>Age: {{john.age}}</h2>
      <h2>Name: {{bobby.name}}</h2>
      <h2>Age: {{bobby.age}}</h2>
    </div>
  `
})

www.youtube.com

Vue Tutorial #2

複雑なロジックはテンプレートに書くのではなく,computed を使って共通化できる.すると {{bobbyFullName}} のように書けるようになる.

const app = new Vue({
  (中略)
  computed: {
    bobbyFullName() {
      return `${this.bobby.first} ${this.bobby.last}`
    },
    johnFullName() {
      return `${this.john.first} ${this.john.last}`
    }
  },
  (中略)
})

ただし,これだとオブジェクトのデータごとに書く必要があるため,filters を使ってさらに共通化できる.すると {{bobby | fullName}} のように書けるようになる.これは bobby というデータオブジェクトを fullName にパイプで渡しているイメージになる.

const app = new Vue({
  el: "#app",
  data: {
    bobby: {
      first: "Bobby",
      last: "Boone",
      age: 25
    },
    john: {
      first: "John",
      last: "Boone",
      age: 35,
    }
  },
  computed: {
    johnAgeInOneYear() {
      return this.john.age + 1;
    }
  },
  filters: {
    ageInOneYear(age) {
      return age + 1;
    },
    fullName(value) {
      return `${value.last}, ${value.first}`;
    }
  },
  template: `
    <div>
      <h2>Name: {{john | fullName}}</h2>
      <h2>Age: {{john.age | ageInOneYear}}</h2>
      <h2>Name: {{bobby | fullName}}</h2>
      <h2>Age: {{bobby.age | ageInOneYear}}</h2>
    </div>
  `
})

www.youtube.com

Vue Tutorial #3

次は v-for ディレクティブを使って,配列のデータオブジェクトを表示することができた.

<div>
  <h2 v-for="friend in friends">{{friend | fullName}}</h2>
</div>

さらに v-model ディレクティブを使うと,自動的に値を書き換えるフォームを作ることができた.これはスゴイ!公式ドキュメントを読んだら,v-modelv-bind:valuev-on:input のシンタックスシュガーと書いてあった.

<div>
  <h2 v-for="friend in friends">
    <h4>{{friend | fullName}}</h4>
    <input v-model="friend.first"/>
    <input v-model="friend.last"/>
  </h2>
</div>

最後は v-on:click でボタンにイベントをバインドした.なお,公式ドキュメントに computedmethods の違いはキャッシュで,計算結果をキャッシュできる場合は computed を使うべきと書いてあった.

vuejs.org

const app = new Vue({
  (中略)
  methods: {
    decrementAge(friend) {
      friend.age = friend.age - 1;
    },
    incrementAge(friend) {
      friend.age = friend.age + 1;
    },
  },
  template: `
    <div>
      <h2 v-for="friend in friends">
        <h4>{{friend | fullName}}</h4>
        <h5>age: {{friend.age}}</h5>
        <button v-on:click="decrementAge(friend)">-</button>
        <button v-on:click="incrementAge(friend)">+</button>
        <input v-model="friend.first"/>
        <input v-model="friend.last"/>
      </h2>
    </div>
  `
})

www.youtube.com

Vue Tutorial #4

Vue コンポーネントを使うと,Vue インスタンスの定義をさらに細分化することができる.ただし,Vue コンポーネントの中のデータは外部から参照できないため,子コンポーネント側で props オプションを使って,親コンポーネントからデータオブジェクトを受け取る必要がある.Vue.js で画面設計をするときに,Vue コンポーネント設計が重要になるという話がよくわかった.

Vue.component('friend-component', {
  props: ['friend'],
  filters: {
    ageInOneYear(age) {
      return age + 1;
    },
    fullName(value) {
      return `${value.last}, ${value.first}`;
    }
  },
  methods: {
    decrementAge(friend) {
      friend.age = friend.age - 1;
    },
    incrementAge(friend) {
      friend.age = friend.age + 1;
    },
  },
  template: `
    <div>
      <h4>{{friend | fullName}}</h4>
      <h5>age: {{friend.age}}</h5>
      <button v-on:click="decrementAge(friend)">-</button>
      <button v-on:click="incrementAge(friend)">+</button>
      <input v-model="friend.first"/>
      <input v-model="friend.last"/>
    </div>
  `
});

const app = new Vue({
  (中略)
  template: `
    <div>
      <friend-component v-for="item in friends" v-bind:friend="item" />
    </div>
  `
})

www.youtube.com

Vue Tutorial #4 が完了した段階で,以下のようなアプリケーションが実装できた.

f:id:kakku22:20180218050849p:plain

(画面イメージ)

Vue Tutorial #5 & Vue Tutorial #6

Vue インスタンスの el を削除しても,コンソールで app.$mount("#app") と実行すれば手動でマウントできることを試した.そして,Vue インスタンスには多くのライフサイクルがあり,例として beforeMountmounted を学んだ.今回は fetch() を使って API を呼び出し,その結果をデータオブジェクトとして利用する一般的な構成を作成した.

また,データを削除する場合は,添字を指定してデータオブジェクトから削除することで,リアクティブに反映される.関連するディレクティブで言えば,v-on:click だけではなく v-on:keyup.13 を使って Enter 押下のイベントを取得したり,v-ifv-else を使ってテンプレートの分岐なども書けた.

const app = new Vue({
    el: "#app",
    data: {
      editFriend: null,
      friends: [],
    },
    methods: {
      deleteFriend(id, i) {
        fetch("http://rest.learncode.academy/api/vue-5/friends/" + id, {
          method: "DELETE"
        })
        .then(() => {
          this.friends.splice(i, 1);
        })
      },
      updateFriend(friend) {
        fetch("http://rest.learncode.academy/api/vue-5/friends/" + friend.id, {
          body: JSON.stringify(friend),
          method: "PUT",
          headers: {
            "Content-Type": "application/json",
          },
        })
        .then(() => {
          this.editFriend = null;
        })
      }
    },
    mounted() {
      fetch("http://rest.learncode.academy/api/vue-5/friends")
        .then(response => response.json())
        .then((data) => {
          this.friends = data;
        })
    },
    template: `
    <div>
      <li v-for="friend, i in friends">
        <div v-if="editFriend === friend.id">
          <input v-on:keyup.13="updateFriend(friend)" v-model="friend.name" />
          <button v-on:click="updateFriend(friend)">save</button>
        </div>
        <div v-else>
          <button v-on:click="editFriend = friend.id">edit</button>
          <button v-on:click="deleteFriend(friend.id, i)">x</button>
          {{friend.name}}
        </div>
      </li>
    </div>
    `,
});

www.youtube.com

www.youtube.com

Vue Tutorial #7

ここから少し難易度が上がり,vue-cli + webpack で Vue.js の環境構築をしながら「単一ファイルコンポーネント」を学んだ.具体的には Tutorial 4 で学んだ Vue コンポーネントを .vue という拡張子のファイルに分割していくことになる.このときに,Vue コンポーネントの中にスタイルも含めるという考え方は個人的には新鮮だった.確かにスタイルまで揃ってこそのコンポーネントだからスッと理解できた.ただし,コンポーネントが増えてくるとスタイルを共通化したくなる未来もありそうで,そのあたりは外部ファイルを読み込むことになるらしく,公式ドキュメントにも書かれていた.またコンポーネント間でスタイルが競合する場合は <style scoped> でスコープを狭めることができる.

vuejs.org

以下に App.vue を載せておく.他のコードは GitHub を参照してもらえればと!

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <HelloWorld/>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld'

export default {
  name: 'app',
  components: {
    HelloWorld
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

www.youtube.com

Vue Tutorial #8

次は Vue Router を使って SPA を実装した.SPA のルーティングなので,基本的にはハッシュイベント(URL 末尾の #)をルーティングする.今回書いた router/index.js は以下のようになる.

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Account from '@/components/Account'
import Contact from '@/components/Contact'
import Friends from '@/components/Friends'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Hello',
      component: HelloWorld
    },
    {
      path: '/friends/:id/:age/:weight',
      name: 'Friends',
      props: true,
      component: Friends
    },
    {
      path: '/contact',
      name: 'Contact',
      component: Contact
    },
    {
      path: '/account',
      name: 'Account',
      component: Account
    }
  ]
})

なお,Vue コンポーネント側でルーティングパラメータは {{$route.params.id}} のように取得できるけど,以下のように props で定義してしまう方法もある.

<template>
  <div>
    <h1>Friends</h1>
    {{id}}
    {{age}}
    {{weight}}
  </div>
</template>

<script>
export default {  
  props: [
    'id',
    'age',
    'weight',
  ]

}
</script>

www.youtube.com

Vue Tutorial #8 が完了した段階で,以下のようなアプリケーションが実装できた.

f:id:kakku22:20180218050925p:plain

(画面イメージ)

Vue Tutorial #9

最後は Vue.js で Store パターンを実装した.以下のような stores/FriendStore.js を用意して,データオブジェクトの操作を Store に閉じ込めている.このあたりは一般的なアーキテクチャなので,特に悩む部分はなかった.今回の Tutorial 対象ではないけど,Flux / vuex の実装は試してみたいと思う.

const FriendStore = {
  data: {
    friends: ["bobby", "billy"],
  },
  methods: {
    addFriend(name) {
      FriendStore.data.friends.push(name);
    }
  }
};

export default FriendStore;

あとは Vue コンポーネントから Store を呼び出せば,あとはリアクティブに実装ができる.

<template>
  <div>
    <h1>My Friends</h1>
    <li v-for="friend in FriendStore.friends">{{friend}}</li>
    <input v-model="newFriend" v-on:keyup.13="addFriend(newFriend)" />
  </div>
</template>

<script>
import FriendStore from "../stores/FriendStore"

export default {
  data() {
    return {
      newFriend: null,
      FriendStore: FriendStore.data,
    };
  },
  methods: {
    addFriend(name) {
      FriendStore.methods.addFriend(name)
      this.newFriend = null;
    }
  }
}
</script>

www.youtube.com

Vue Tutorial #9 が完了した段階で,以下のようなアプリケーションが実装できた.

f:id:kakku22:20180218050939p:plain

(画面イメージ)

まとめ

  • LearnCode.academy「Vue Tutorial」を受講して,はじめて Vue.js を試してみた
  • 「Vue.js は気になるけどまだ試したことがない」という人にオススメ!
  • MVVM な設計,コンポーネント思想,便利な Vue ディレクティブなど,Vue.js を採用するメリットを感じることができた
  • 公式ドキュメントも,日本語ドキュメントも,非常に詳しく書かれていて素晴らしかった

開発ツールなど

今回はじめて Vue.js を書いたので,まだ深く使えていないけど,VS Code + Vetur でコードを書いて,デバッグのときは Chrome 拡張の vue-devtools を使った.

github.com

github.com

関連記事

jQuery を活用して SPA を実装するのを学ぶには「サーバーレスシングルページアプリケーション」もオススメ!

kakakakakku.hatenablog.com