22
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

【Vue.js + Poke API + SPA】ポケモンをつかまえて図鑑に登録するWebアプリをつくった 

はじめに

こんにちは!
最近はすっかりVue.jsにハマってしまったので、Poke APIを利用して、Pokemon GOみたいなサイトを個人でつくってみました!

Vue CLIにおける開発からリリースまでの流れは非常に簡単でわかりやすく、Vue歴3ヶ月の私でもすぐに実践できたので、参考にしてもらえると嬉しいです!

つくったもの

エリア選択画面

スクリーンショット 2020-02-01 12.32.49.png

まずはもり、へいち、やま、みずうみ、うみの5つのエリアからいきたい場所を選択します。
エリアによって出現するポケモンが異なります。

ポケモン選択画面

スクリーンショット 2020-02-01 13.02.37.png

3~11匹のポケモンがランダムに出現するのでクリックして戦闘画面に移ります。
ポケモンによって出現率にばらつきがあり、伝説のポケモンは結構レアです🤗

戦闘画面

スクリーンショット 2020-02-01 13.06.01.png

捕獲後

スクリーンショット 2020-02-01 13.07.05.png

ポケモンずかん

スクリーンショット 2020-02-01 13.08.03.png

つかまえたポケモンはずかんに登録されます!
すべてのポケモンの登録を目指しましょう✨

さいきんつかまえたポケモン

スクリーンショット 2020-02-01 13.10.03.png

さいきんつかまえたポケモンが入手順に表示されます!

つかった技術

Vue CLIで開発をおこない、GitHub Pagesで公開しました!
GitHub Pagesは静的サイトならコミットするだけで簡単にリリースができます。

さらに、Vue CLIの開発にあたって以下のライブラリを使用しました。
開発の途中から必要になってもすぐにインストールして組み込めるので素敵ですね✨

Vue Router

VueRouterはVue.jsの公式のルーターです。
Vue.jsにおけるSPAを実現するために使用されます。

Vuex

VuexはVue.jsの公式のデータフローの状態管理ライブラリです。
後ほど今回のデータフローについて説明しますが、https://momentjs.com/)簡単に説明すると、アプリケーションで保持されるデータを`store`と呼ばれる一つの場所で管理して、そこから読み書きをしよう、というものです。

axios

Promise HTTP通信を行うためのライブラリです。

vuex-persistedstate

vuexにおいて、localStorageへ永続化する処理を隠蔽してくれるライブラリです。
Vuexにプラグインとして定義することで自動でcommitされたデータをlocalStorageへ保存してくれます。

vuex-router-sync

Vuexの保持するstoreとrouterの情報を同期してくれるライブラリです。
Vuexのstore内からルーティングのデータを取得することができるようになります。

vue-fontawesome

FontAwesome(多数のwebアイコンを使用できるサービス)をVue.jsで使用するためのライブラリです。

vue-flash-message

Vue.jsでフラッシュメッセージが使用できるライブラリです。
flash.gif

vue-burger-menu

Vue.jsでハンバーガーメニューを使用できるライブラリです。
burger.gif

anime.js

JavaScriptで簡単にアニメーションをつけることができるライブラリです。

moment.js

JavaSctiptで日付のフォーマットをするライブラリです。

APIの仕様確認

今回のWebアプリはPoke APIを利用してつくられています。
Poke APIはRESTfulなAPIです。教育用のAPIのようですが、データ量も豊富でドキュメントもかなりしっかりしています。

とりあえず叩いてみましょう。
https://pokeapi.co/api/v2/

スクリーンショット 2020-02-01 21.28.17.png

なにも指定しないでAPIを叩くと取得できるデータの一覧が表示されます。みてわかるようにめちゃくちゃあります。
今回はこのなかから以下のAPIを利用します。

  • pal-park-area
  • pokemon
  • pokemon-species

par-park-areaは各エリアごとにの出現ポケモンと出現率を提供してくれます。
pokemonはポケモンの技、特性、タイプなどのデータを提供します。
pokemon-speciesはポケモン名や図鑑説明文などのデータです。基本的に日本語名はここで取得します。

ところで、APIの一覧がURLで提供していますね。
Poke APIはこのように、レスポンスとしてlinkを提供するHATEOASという形で提供されています。

例えば、ポケモン一覧取得できる最初のポケモンのタイプ名を取得する例を考えてみましょう。

{
  "count": 964,
  "next": "https://pokeapi.co/api/v2/pokemon/?offset=20&limit=20",
  "previous": null,
  "results": [
    {
      "name": "bulbasaur",
      "url": "https://pokeapi.co/api/v2/pokemon/1/"
    },
    {
      "name": "ivysaur",
      "url": "https://pokeapi.co/api/v2/pokemon/2/"
    },

https://pokeapi.co/api/v2/pokemon/を叩いたときのレスポンスです。
結果として、ポケモン一覧が返されますが、詳細情報は記載されておらず、リンクをたどっていきます。

それではhttps://pokeapi.co/api/v2/pokemon/1/を叩いてみましょう。


"abilities": [
    {
      "ability": {
        "name": "chlorophyll",
        "url": "https://pokeapi.co/api/v2/ability/34/"
      },
      "is_hidden": true,
      "slot": 3
    },
    {
      "ability": {
        "name": "overgrow",
        "url": "https://pokeapi.co/api/v2/ability/65/"
      },
      "is_hidden": false,
      "slot": 1
    }
  ],
  "base_experience": 64,
--- 省略 ---
"types": [
    {
      "slot": 2,
      "type": {
        "name": "poison",
        "url": "https://pokeapi.co/api/v2/type/4/"
      }
    },
    {
      "slot": 1,
      "type": {
        "name": "grass",
        "url": "https://pokeapi.co/api/v2/type/12/"
      }
    }
  ],
  "weight": 69
}

レスポンスの中身はフシギダネの詳細情報ですが、なんと10326行ものレスポンスが返されます。

レスポンスの最後のほうに、タイプに関する情報がありましたが、どうやら英語名で返却されているようですね。
日本語のタイプ名を取得するためには、さらにタイプの詳細情報までたどっていきます。
さあ、https://pokeapi.co/api/v2/type/4/を叩きましょう。

--- 省略 ---
 "name": "poison",
  "names": [
    {
      "language": {
        "name": "ja-Hrkt",
        "url": "https://pokeapi.co/api/v2/language/1/"
      },
      "name": "どく"
    },
    {
      "language": {
        "name": "ko",
        "url": "https://pokeapi.co/api/v2/language/3/"
      },
      "name": ""
    },
    {
      "language": {
        "name": "fr",
        "url": "https://pokeapi.co/api/v2/language/5/"
      },
      "name": "Poison"
    },
    {
      "language": {
        "name": "de",
        "url": "https://pokeapi.co/api/v2/language/6/"
      },
      "name": "Gift"
    },
    {
      "language": {
        "name": "es",
        "url": "https://pokeapi.co/api/v2/language/7/"
      },
      "name": "Veneno"
    },
    {
      "language": {
        "name": "it",
        "url": "https://pokeapi.co/api/v2/language/8/"
      },
      "name": "Veleno"
    },
    {
      "language": {
        "name": "en",
        "url": "https://pokeapi.co/api/v2/language/9/"
      },
      "name": "Poison"
    }
  ],
--- 省略 ---

ようやく見つけましたね!
ここまでの流れは、私達が普段ウェブサイトを閲覧する際にリンクをたどっていくのとよく似ていますね。
HATEOASは、入り口のエンドポイントを知っていれば、すべてのAPIの機能へアクセスすることが可能です。

さらに、画像データの取得のためにこちらのAPIも使用しています。
GitHub - fanzeyi/Pokemon-DB: A Pokemon database in JSON format.

データフロー

Vuexを用いた状態管理管理をします。
Vuexのによる状態管理は以下の図のようになります。

vuex.png

ポケモンをゲットしてから図鑑に登録するまでのデータフロー

さきほどの図をもとに設計します。

ポケモン出現

Actions

Buttle.vue Componentsを起点にしたアクションとなります。
Buttle.vue Componentsはルーティングのparamsとしてポケモンの名前を受け取るので、ポケモン名をもとにAPIを叩きます。

例)ラティオスと遭遇した場合
router: `/buttle/latios


import axios from 'axios'

const actions = {
  async fetchPokeData ({ state, commit, dispatch }) {
    try {
      // vuex-router-syncにより、vuexからrouterのparamsを取得
      const result1 = await axios.get(`${state.baseUrl}pokemon-species/${state.route.params.name}`)
      const species = result1.data

      const result2 = await axios.get(species.varieties[0].pokemon.url)
      const pokemon = result2.data

      dispatch('fetchTypes', pokemon)
      commit('setSpeceis', species)
      commit('setPokemon', pokemon)
    } catch {
      alert('通信エラーが発生しました。')
    }
  }
}

Mutations

APIから、pokemonpokemon-speciesを取得して、それぞれのミューテーションにcommitします。

const mutations = {
  setSpeceis(state, speceis) {
    state.species = speceis
  },
  setPokemon(state, pokemon) {
    state.pokemon = pokemon
  }
}

getters

上記の図にはないですが、さきほども見たとおりPoke APIからは膨大なデータ量のレスポンスが送られてくるのでここで必要なデータに加工します。

const getters = {
  pokeData (state, getters) {
    // 必要なデータを纏めたものを返します。

    return {
      id: state.pokemon.id,
      name: getters.getI18nName,
      englishName: state.pokemon.name,
      genera: getters.getI18nGenera,
      type: getters.getI18nType,
      height: state.pokemon.height,
      weight: state.pokemon.weight,
      flavorText: getters.getI18nFlavorText,
      habitat: state.species.pal_park_encounters[0].area.name,
      capture_rate: state.species.capture_rate,
      like: false,
    }
  },
  // 日本語データを取得します。他の日本語取得gettersも似たような処理をします。
  getI18nName(state) {
    const names = state.species.names
    const result = names.find(v => v.language.name === state.local)
    return result.name
  },
}

render

リアクティブシステムによるストアの状態変更を検知後、描画関数であるrenderを実行して表示内容を更新します。

ポケモンを図鑑に登録

ポケモンの捕獲に成功したら、APIから取得したデータの永続処理を行います。
データの永続化の方法として、vuex-persistedstateによるローカルストレージへの保存が行われるので、今回はactionsは存在しません。

Mutations

registPokedexregistRecentryGetの2つのミューテーションにcommitします。
registPokedexは、図鑑にデータを登録する処理なので、すでに登録済みのデータだった場合にはなにもしません。
対してregistRecentryGetはポケモンの重複があっても登録します。

const mutations = {
  registPokedex(state, pokeData) {
    // pokedexの初期データは493の長さをもった空のオブジェクトをもった配列です。
    if (!state.pokedex[pokeData.id - 1].hasOwnProperty('id')) {
      state.pokedex[pokeData.id - 1] = pokeData
    }
  },
  registRecentryGet(state, pokeData) {
    state.recentryGet.unshift(pokeData)
  },
}

render

リアクティブシステムによるストアの状態変更を検知後、描画関数であるrenderを実行して表示内容を更新します。
登録されたら、図鑑のページとさいきんつかまえたポケモンのページが更新されています。

ルートフロー

routingの設計は以下の通りです。

'\' - \adventure - \field\:name - \buttle\:name
    - \pokedex
    - recentry-get

プロジェクトの作成からリリースまでの流れ

最後に、実際の実装の流れを見ていきましょう!初心者でもとても簡単にできるように機能が提供されています。

プロジェクトの作成

パッケージ管理ツールとして、npm(Node Package Manager)を利用します。
npmは、インストールしたパッケージのバージョンをpackage.jsonで管理して、package.jsonから一括で依存関係をインストールすることができます。
npmのインストールは、Node.jsをインストールすると付属してダウンロードされます。
実際にnpm installでインストールされたパッケージはnode_modulesに格納されます。
Node.jsのインストールに関して、以下のサイトに詳しいです。
https://qiita.com/kyosuke5_20/items/c5f68fc9d89b84c0df09

また、モジュールの管理はwebpackによって処理されます。
webpackを簡単に表すと、モジュールで分割されたファイルをバンドル(まとめる)役割をします。
バンドルファイルを出力することを、ビルドと言います。

webpackを利用することで、機能ごとにファイルを分割(モジュール化)することが可能です。
さらに、npmでインストールされた外部モジュールを利用することも可能です。

Node.jsがインストールできたらあとは簡単です。
まずは@vue/cliをインストールします。

npm install -g @vue/cli

次に、vue create (プロジェクト名)を開発環境の構築が完了します。

vue create Pokemon-get-Adventure

プロジェクトの作成は対話形式で進みます。

Vue CLI v4.1.2
? Please pick a preset: 
  default (babel, eslint) 
❯ Manually select features 

はじめに、デフォルトの設定を使用するかマニュアルの設定を使用するか聞かれます。
今回はVue RouterとVuexを使用したいのでマニュアルを選択します。
これらのライブラリはプロジェクトを作成する段階で導入してくれます。

? Check the features needed for your project: 
 ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◉ Router
 ◉ Vuex
 ◯ CSS Pre-processors
 ◉ Linter / Formatter
 ◯ Unit Testing
❯◯ E2E Testing

デフォルトの設定に加えてRouterとVuexを選択しました。
あとはすべてそのままエンターで進めて、プロジェクトの完成です!

🎉  Successfully created project Pokemon-get-Adventure.
👉  Get started with the following commands:

 $ Pokemon-get-Adventure.
 $ npm run serve

指示どおりにプロジェクトのディレクトリに移動し、npm run serveでサーバーを起動すれば、アプリケーションを起動することができます。

cd Pokemon-get-Adventure
npm run serve

さらに必要なライブラリもインストールしておきます。

npm install axios --save
npm install vuex-persistedstate --save
--- 省略 ---

はじめは少ない機能から始めて、必要になったら随時ライブラリをインストールするというスタイルが取れるのが良いですね。

コードの実装

プロジェクトの作成に成功したらアプリケーションを実装していきましょう。
今回のコードの完全版はここで見れます。

GitHub:https://github.com/azukiazusa1/Pokomeon-get-Adventure

リリース

コードが完成したら早速リリースしましょう!
今回はGitHub Pagesを使用するので、そのための設定が必要です。

GitHub Pages用の設定

GitHub Pagesではdocsフォルダ配下のを公開範囲に設定できるので、ビルドの出力先をdocsに設定します。
プロジェクトのルート配下にvue.config.jsファイルを作成して、以下の設定を記述します。

module.exports = {
  publicPath: '/github-pages-example-with-vue-router',
  outputDir: 'docs',
  filenameHashing: false,
  productionSourceMap: false,
}

こちらを参考にさせていただきました:Vue CLI で作った Vue Router 利用プロジェクトを GitHub Pages で公開する方法

GitHubのレポジトリからsettingを選択して、GitHub Pagesの設定のsourceを、master branch/docs folderを選択します。
スクリーンショット 2020-02-01 22.36.15.png

さらに、Vue Routerのhistoryモードを使用している場合には、更に設定が必要です。
公式にもあるように、サーバーの適切な設定をすることが必要です。

history モードを使用する時は、URL は "普通" に見えます e.g. ttp://oursite.com/user/id。美しいですね!

しかしながら一点問題があります。シングルページのクライアントサイドアプリケーションなので、適切なサーバーの設定をしないと、ユーザーがブラウザで直接 http://oursite.com/user/id にアクセスした場合に 404 エラーが発生します。

しかし、GitHub Pagesを利用する際には、サーバーの設定をすることができないのでここではハック的な方法を利用します。

このページを参考にさせていただきました。:S(GH)PA: The Single-Page App Hack for GitHub Pages

GitHub Pagesでは、404エラーが発生したときに404.htmlファイルを表示するようになっているので、publicフォルダ配下に`404.htmlに以下の記述をします。


<script>
  sessionStorage.redirect = location.href;
</script>
<meta http-equiv="refresh" content="0;URL='/REPO_NAME_HERE'"></meta>

更に、index.htmlに以下を追記します。Vueが読み込まれる前に記述しないといけないようなので、

タグ内に記述しておくと万全です。

<script>
  (function(){
    var redirect = sessionStorage.redirect;
    delete sessionStorage.redirect;
    if (redirect && redirect != location.href) {
      history.replaceState(null, null, redirect);
    }
  })();
</script>

これで全ての設定は完了です!

ビルド

設定が完了したら、npm run buildでビルドをします。
docsフォルダにファイルが出力されたのを確認できたら、レポジトリにプッシュしましょう。

少し時間をあけたら、サイトが公開されているはずです!

さいごに

はじめて1からWebアプリを作ってみましたが、開発環境の構築から最後までとても簡単にできて、なおかつ学習コストも高くないので本当に楽しかったです!

みなさんもポケモンたくさん捕まえてみてください☺️

参考

Vue.js
Vue Router
Vuex
JavaScript - MDN - Mozilla
Vue.js入門 基礎から実践アプリケーション開発まで
Vue.jsとDjango REST FrameworkでSPAなWebをやる時の勘どころ - HATEOASと非同期処理(の触り)
モーダルウィンドウ | 基礎から学ぶ Vue.js
JavaScriptで重みを付けてランダムに出現させる。(重み付き乱択)
Vue CLI で作った Vue Router 利用プロジェクトを GitHub Pages で公開する方法

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
22
Help us understand the problem. What are the problem?