通常、Vue.jsにてアプリケ―ション内のコンテンツ切り替えを行うとWEB上のURLは変化しない。いわゆる、単一のWEBページでアプリケーションが完結するSPA(Single Page Application)として機能しているからだ。この方式は必要最小限のデータ通信によって、ページ内のコンテンツ更新が局所化するので、アプリケーション全体としてのパフォーマンスを最大化させられるメリットがある。
 しかしながら、ネイティブアプリの代替としてWEBアプリケーションを開発するケース以外では、システム的に現在のルートが把握しづらいというデメリットがある。今回のアプリケーションは別に単一のWEBページにこだわる必要もないので、URLベースなルーティングを採用して(表面的には疑似的ではあるのだが)ページ遷移を行っているような建付けにする。

Vue Routerの導入

 というわけで、Vue.js上でルーティングを管理するためには、公式プラグインの「Vue Router」を使えばよい。早速インストールしよう。

yarn add vue-router

 インストールされたら、ルータファイルを作成する。

cd src
mkdir router
touch router/index.js

 あとは、ルータファイルsrc/router/index.jsにルーティングの定義をしていくことになる。とりあえずは、今回のアプリケーションのホーム画面となるルートを設定しておく。

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  }
]

const router = new VueRouter({
  mode: 'history',
  routes,
  scrollBehavior(to, from, savedPosition){
    if (savedPosition) {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve(sevedPosition)
        })
      })
    } else {
      return {x:0, y:0}
    }
  },
})

export default router

 次に、Vueアプリをインスタンス化するディスパッチャ・ファイルsrc/main.jsにて、上記のルータファイルをインポートする。

import Vue     from 'vue'
import App     from './App.vue'
import router  from './router'
import vuetify from './plugins/vuetify';

Vue.config.productionTip = false

new Vue({
  router,
  vuetify,
  render: h => h(App)
}).$mount('#app')

 これで、VueアプリでVue Routerが使用できるようになったので、初期ルートとして定義したホーム画面用のコンポーネントHomeを作成する。ルータファイル内でルーティング用のコンポーネントパスをviews/Home.vueとしたので、それに従う。前回作成したメインコンテンツエリア用のダミーコンポーネントsrc/components/MainView.vueを暫定のホーム画面コンポーネントとしてしまおう。

cd src
mkdir views
mv components/MainView.vue views/Home.vue

 そして、ルートコンポーネントApp.vueでのメインコンテンツエリア用のコンポーネント設定をルータ経由に変更する。

<template>
  <v-app>
    (...省略...)
    <v-main>
      <v-container
        fluid
      >
        <router-view />
      </v-container>
    </v-main>
    (...省略...)
  </v-app>
</template>

<script>
import SideMenu from './components/SideMenu'
import Footer   from './components/Footer'

export default {
  name: 'App',

  components: {
    SideMenu,
    Footer,
  },
  (...省略...)
}
</script>


 今まで<MainView />として個別コンポーネントをインポートしていた部分を<router-view />と、Vue Router経由でのルートのインポートに変更している。

 では、ビルドしてみよう。

yarn serve

 前回と同じ画面が表示されたはずだ。では、現在のルーティングの情報をコンポーネント側で取得してみる。Home.vue内に下記のようなcreate()メソッドを追加してみる。

<script>
export default {
  (...省略...)
  create() {
    console.log(this.$route)
  },
}
</script>

 追加した後、ブラウザのデベロッパーツールのコンソールを確認してみると、ルートのオブジェクトが出力される。Vue Routerの導入はこれで完了だ。

ホーム画面の構成

 今回のアプリケーションのホーム画面では、

  • 装備の検索と変更
  • 装備のスキル状況の一覧
  • 装備後のステータス表示

──を一気にやりたい。ゲーム本編だと、Switch自体の画面の解像度の制限もあって、別画面に切り替えたり、サブメニューを表示したりしないとこれらの情報を一元的に確認できないのが不便なのだ。特に、各装備のスキルレベルをテーブル形式で表示できる下記のUIは非常に優れているので、これをベースにしたUIを実装したいと考えている。

スキルレベル内訳

 懸念点としては、スマホなどの低解像度向けにはこれらの全情報を一気に表示するのは難しいことだ。まぁ、今の段階ではそれは一旦置いておいて、後から考えようかね……w
 では、それぞれのコンポーネントを作成し、Home.vueに統合化して行こうか。

ホーム画面のモックアップ

 いやはや、モックアップ作っている間にモンハンライズのVer.2.0アップデートが入ったこともあって、だいぶ遊んでたよ……。
 で、こちらのツール開発も中断していたのだが、連休も明けたので、一気に仕上げてみた。

 各コンポーネント単位での詳しい説明は省くが、詳細なコードについては、GitHubのこちらのリポジトリを参照して欲しい。

 ここまでくると、だいぶアプリケーションの動きが想像できるようになっている。このモックアップには既にローカル環境専用のダミーデータを読み込ませているので、この段階でも実際のアプリの動作をほぼほぼ再現できている。では、そのやり方を紹介していこう。

Vueアプリの共通処理をミックスイン化する

 Vueアプリにおいて、親・子を問わずに各コンポーネントから共通的に利用したい処理は、ミックスインとしてプラグイン化してバインドすると、いちいちコンポーネント側でメソッド定義する必要がなくなる。
 ダミーデータを読み込ませるにあたって、現在のアプリケーションの稼働環境がローカル環境なのか本番環境なのかを判定して条件分岐させるのだが、そういうグローバルな処理等は共通処理としてミックスイン化するのが良い。
 では、ミックスイン化するプラグインファイルfunctions.jsを作成しよう。

cd src/plugins
touch functions.js

 プラグインファイルの中身はこんな感じだ。

export default {
  methods: {
    // Determine the host and switch debug mode
    isLocalhost: function() {
      let hostName = document.location.hostname
      return hostName === 'localhost' || hostName === '127.0.0.1'
    }
  }
}

 アプリケーションの実行環境がローカル環境かどうかを判定するだけの共通メソッドが含まれている。今はこれだけだが、次回以降で導入する予定のAxios用ハンドラなんかもここに追加していく形になる。なお、このプラグインファイルはアプリ内の全Vueファイルから使用されるため、createdやmountedなどのライフサイクル用に処理を追加してしまうと、律義にすべてのコンポーネントでその処理が実行されてアプリ自体のパフォーマンスが落ちるので、留意しておく必要がある。
 で、作成したプラグインファイルはsrc/main.jsでVue.jsにミックスインとしてインポートする。main.jsを下記のように更新しよう。

import Vue     from 'vue'
import App     from './App.vue'
import router  from './router'
import vuetify from './plugins/vuetify'
import functions from './plugins/functions'
import './styles/mhrss.scss'
import './styles/mhrssi.scss'

Vue.config.productionTip = false

Vue.mixin(functions)

new Vue({
  router,
  vuetify,
  render: h => h(App)
}).$mount('#app')

 余談だが、独自のカスタムスタイル等を適用したい場合も、ここでSCSSファイル等をインポートするのがベストだと思う。Vue.jsではコンポーネントファイル内にスコープドスタイルとしてCSSを埋め込むこともできるが、あれをやるとコンポーネントの可読性が落ちて保守がしづらくなるんだよね……。

 これで、コンポーネントファイル側で、定義したメソッドが使えるようになる。例えば、App.vueのcreatedで使う時は、

created() {
  if (this.isLocalhost()) {
    console.log("It's running on localhost right now.")
  }
},

──のようにすれば良い。

モックアップ用のダミーデータを準備する

 モックアップで利用するダミーデータは、最終的にデータベースからデータを取得した際の形式を意識して作成する必要がある。今回のアプリではバックエンドから取得するデータはJSON形式を想定しているので、ダミーデータもJSONファイルとして準備した。

cd public
touch mock_data.json

 ダミーデータの配置場所だが、src/assetsの下とかでもイイが、今回はプロジェクトディレクトリ直下のpublicディレクトリの下にしている。ここでは、JSONファイルの内容の一部を紹介しておく。

{
  "weapons": [
    {
      "id": 1,
      "name": "ハイニンジャソード",
      "type": 2,
      "rarity": 1,
      "rank": 2,
      "attack": 200,
      "sharpness": {},
      "affinity": 100,
      "defense_bonus": 0,
      "element1": 0,
      "element2": 0,
      "elem1_value": 0,
      "elem2_value": 0,
      "slot1": 1,
      "slot2": 1,
      "slot3": 1,
      "forging_materials": {},
      "upgrade_materials": {},
      "forge_funds": 0,
      "upgrade_funds": 55000,
      "tree": "ギルド武器派生",
      "rampage_skills": {}
    }
  ],
  "armors": [
    {
      "id": 1,
      "name": "練達の羽根飾り",
      "series": "練達の羽根飾り",
      "part": 0,
      "rarity": 2,
      "rank": 2,
      "defense": 56,
      "level": 1,
      "max_level": 11,
      "fire_resistance": 0,
      "water_resistance": 0,
      "thunder_resistance": 0,
      "ice_resistance": 0,
      "dragon_resistance": 0,
      "slot1": 0,
      "slot2": 0,
      "slot3": 0,
      "skills": {"剥ぎ取り名人": 1},
      "forging_materials": {},
      "forge_funds": 0
    },
...(以下省略)
}

 あとは、このダミーデータを使いたいコンポーネント側でファイルをインポートすれば良い。例えば、「防御ステータス」用のコンポーネントDefenseStatus.vueでの記述を紹介しておく。

<script>
import Parameter from '@/components/Parameter'
import mockData from '@/../public/mock_data.json'

export default {
  name: 'DefenseStatus',

  components: {
    Parameter,
  },

  data: () => ({
    labels: {
      title: '防御ステータス',
    },
    totals: {
      'defense': 0,
      'fire_resistance': 0,
      'water_resistance': 0,
      'thunder_resistance': 0,
      'ice_resistance': 0,
      'dragon_resistance': 0,
    }
  }),

  created() {
    for (let key in this.totals) {
      this.totals[key] = mockData.armors.reduce((acc, cur) => acc + parseInt(cur[key], 10), 0)
    }
  },
}
</script>

 Vue.jsではインポートしたJSONファイルをそのまま使用できるので、ダミーデータにとどまらず利用シーンは多いだろう。


 と、こんな感じでモックアップが出来たので、あとはレビューだ。レビューといっても、レビューイ=レビュアー=私なので、まぁ、自分で納得できれば合格なのであるw
 業務で開発するアプリなどでは、ここで色々とイチャモ……いや、要望が出てくるので、それを取り込みながらブラッシュアップして行くことになるだろう。

 私のレビューについては、はい、OKっす (完了w)

 ちゅーわけで、今回はここまで。

 次回は、Axios入れてサーバ側との連携部分を作るぜ。