そんな今日この頃の技術ネタ

本家側に書くほどでもない小ネタ用

Vue.jsで最低限文化的なクリッカーを作ってみる

f:id:blue1st:20171105203901p:plain

 個人的に新しいJavascriptフレームワークを素振りする際の定番がクリッカー(某クッキーのアレ)だったりする。今回はごくごく簡素なクリッカーを作りつつVue.jsの使い方を紹介したいと思う。

github.com

ひな型の作成

 ペライチ想定なのでvue-routerなし、テストとかも今回はなしで。

$ vue init webpack vue-clicker

? Project name vue-clicker
? Project description simple clicker
? Author *****
? Vue build standalone
? Install vue-router? No
? Use ESLint to lint your code? Yes
? Pick an ESLint preset Standard
? Setup unit tests with Karma + Mocha? No
? Setup e2e tests with Nightwatch? No

   vue-cli · Generated "vue-clicker".

   To get started:

     cd vue-clicker
     npm install
     npm run dev

   Documentation can be found at https://vuejs-templates.github.io/webpack

 また、必要なパッケージをインストールしておく。

$ cd vue-clicker
$ yarn
yarn
yarn install v1.2.1
info No lockfile found.
[1/5] 🔍  Validating package.json...
[2/5] 🔍  Resolving packages...
[3/5] 🚚  Fetching packages...
[4/5] 🔗  Linking dependencies...
[5/5] 📃  Building fresh packages...
success Saved lockfile.
warning Your current version of Yarn is out of date. The latest version is "1.3.2" while you're on "1.2.1".
info To upgrade, run the following command:
$ curl -o- -L https://yarnpkg.com/install.sh | bash
✨  Done in 126.18s.


画面設計

 クリッカーとしての最低要件を考えると

  • カウンター
  • カウントアップするためのボタン
  • 一定のカウントになるとアンロックされるレベルアップ機能

なんかが必要となるだろう。

 大まかな画面構成が決まったら、それをパーツごとに分解してひとまず見た目だけのモックを作成していく。

  • カウンター: 'src/components/Counter.vue'
<template>
  <p class="count">12345</p>
</template>

<script>
export default {
  name: 'counter'
}
</script>

<style scoped>
.count {
  font-size: xx-large;
}
</style>
  • カウントアップボタン: src/components/CountupButton.vue
<template>
  <button>+1</button>
</template>

<script>
export default {
  name: 'countup-button'
}
</script>

<style scoped>
button {
  font-size: x-large;
}
</style>

* ランクアップメニュー: 'src/components/RankupMenu.vue'

<template>
  <div class="menu">
    <p>ほげほげ</p>
    <button>0 (3/10)</button>
  </div>
</template>

<script>
export default {
  name: 'rankup-menu'
}
</script>

<style scoped>
.menu {
  border: 1px dotted black;
  font-size: large;
}
</style>


 また、作成したコンポーネントを本体たるsrc/App.vueで読み込んで並べていく。今回使用したwebpackの設定ではimportの際に*.vue*.jsの拡張子は省略できる。

<template>
  <div id="app">
    <div id="main">
      <counter></counter>
      <countup-button></countup-button>
    </div>
    <div id="side">
      <rankup-menu></rankup-menu>
      <rankup-menu></rankup-menu>
      <rankup-menu></rankup-menu>
      <rankup-menu></rankup-menu>
    </div>
  </div>
</template>

<script>
import Counter from './components/Counter'
import CountupButton from './components/CountupButton'
import RankupMenu from './components/RankupMenu'
export default {
  name: 'app',
  components: {
    Counter,
    CountupButton,
    RankupMenu
  }
}
</script>

<style>
div#app {
  width: 100%;

  display: -webkit-inline-flex;
  display: inline-flex;

  -webkit-flex-direction: row;
  flex-direction: row;
}
div#main {
  width: 70%;

  border: 1px solid blue;

  display: -webkit-inline-flex;
  display: inline-flex;

  -webkit-flex-direction: column;
  flex-direction: column;
}
div#side {
  width: 30%;

  border: 1px solid red;

  display: -webkit-inline-flex;
  display: inline-flex;

  -webkit-flex-direction: column;
  flex-direction: column;
}
</style>

 ここまでで一旦yarn devなんかで起動して見た目を確認、調整を行う。

ひとまず画面構成まで · blue1st/vue-clicker@ae57923 · GitHub


機能の実装

 さっそくハリボテに実際の機能を与えていく。


親要素から子要素への変数の受け渡し

https://jp.vuejs.org/v2/guide/components.html#プロパティ検証

 まずは何と言ってもカウントを管理する必要がある。これはアプリケーション全体で共通して使用されるデータなので、App.vueに変数countとして定義する。

data () {
  return {
    count: 0
  }
}

 この値をCounterコンポーネントに渡す。これはHTMLタグに:val="VALUE"のような形で与えれば良い。

<counter :count="count"></counter>


 また、受け渡される側であるcomponents/Counter.vueはそれをpropsとして受け取る。

props: {
  count: Number
}

 そして、それをテンプレートで表示する。

<p class="count">{{count}}</p>

propsによるデータの受け渡し · blue1st/vue-clicker@6ffc0af · GitHub


子要素から親要素へのアクションの受け渡し

 クリッカーなので、ボタンを押すことでカウンターの数値を増やしたい。今回のコードで言い換えればcomponents/CountupButton.vueのクリックイベントをトリガーとしてApp.vuecountを増減させるということである。

 ひとまず子要素であるcomponents/CountupButton.vueにイベント発行を行う仕組みを作ってみる。

 タグに@event="Method"と記述することで記述したイベントが生じた際に実行するメソッドを定義できる。

<button @click="countup(1)">+1</button>

 また、ここで叩かるメソッドをmethods項で定義できる。この中で$emitを用いてこの要素自体がaddというイベントを発火するものとしてみる。

methods: {
  countup (num) {
    this.$emit('add', num)
  }        
}

https://jp.vuejs.org/v2/api/#vm-emit


 発行されたaddイベントをApp.vueで受け取り、カウントアップするようにしてみる。

<countup-button @add="addCount"></countup-button>
methods: {
  addCount () {
    this.count += 1
  }
}

https://jp.vuejs.org/v2/api/#v-on

 このようにして子要素が間接的に親要素の変数をいじることができる。

emit/onでカウントアップを実装 · blue1st/vue-clicker@ba46c42 · GitHub


ランクアップメニューを作る

 ランクアップメニューはほぼ同じものが沢山作る必要がある。このあたりはmixinsやextendsを用いることで効率的に作成できる。

https://jp.vuejs.org/v2/api/#mixins

 流石に面倒になってきたので詳細は省く。

add RankupMenu · blue1st/vue-clicker@e5b2d27 · GitHub


ビルド&Github Pagesとしてデプロイ

 そんなこんなで満足いくものができたらビルドする。

 まず作業用のブランチを作成。

$ git checkout -b release

 ビルドの前に、プロジェクトのGithub Pagesのはhttps://{ACCOUNT}.github.io/{PROJECT}/というURLになるため、それ用にビルドの設定を修正しておく。 config/index.jsassetsPublicPath項を今回は/から/vue-clicker/に変更。

 そんなこんなでビルド。

yarn build
yarn run v1.2.1
$ node build/build.js
Hash: 9d6fed5d68fbd8909140
Version: webpack 3.8.1
Time: 7031ms
                                              Asset       Size  Chunks             Chunk Names
              static/js/app.f23ef1ad0fab924dbf86.js    3.45 kB       0  [emitted]  app
           static/js/vendor.74aab6a9f9d88781cc24.js    94.8 kB       1  [emitted]  vendor
         static/js/manifest.63dbf12684c82d83fc78.js    1.49 kB       2  [emitted]  manifest
static/css/app.be925e3357c3d239f2b1180af9f3dd63.css  619 bytes       0  [emitted]  app
          static/js/app.f23ef1ad0fab924dbf86.js.map      36 kB       0  [emitted]  app
       static/js/vendor.74aab6a9f9d88781cc24.js.map     791 kB       1  [emitted]  vendor
     static/js/manifest.63dbf12684c82d83fc78.js.map    14.2 kB       2  [emitted]  manifest
                                         index.html  448 bytes          [emitted]

  Build complete.

  Tip: built files are meant to be served over an HTTP server.
  Opening index.html over file:// won't work.

✨  Done in 11.18s.

 出来上がったdistはgitignoreで無視されてしまうのでforceオプション付きのadd/commit。

$ git add --force dist/
$ git commit
[release 4ebe68e] release file
 8 files changed, 16 insertions(+)
 create mode 100644 dist/index.html
 create mode 100644 dist/static/css/app.be925e3357c3d239f2b1180af9f3dd63.css
 create mode 100644 dist/static/js/app.f23ef1ad0fab924dbf86.js
 create mode 100644 dist/static/js/app.f23ef1ad0fab924dbf86.js.map
 create mode 100644 dist/static/js/manifest.63dbf12684c82d83fc78.js
 create mode 100644 dist/static/js/manifest.63dbf12684c82d83fc78.js.map
 create mode 100644 dist/static/js/vendor.74aab6a9f9d88781cc24.js
 create mode 100644 dist/static/js/vendor.74aab6a9f9d88781cc24.js.map

 dist以下を指定してgh-pagesというブランチとしてプッシュするとGithub Pagesとして公開できる。

git subtree push --prefix dist/ origin gh-pages


 そんなこんなで実際に出来上がったのがこちら。

vue-clicker