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

#はじめに
こんにちは!
最近はすっかりVue.jsにハマってしまったので、[Poke API](https://pokeapi.co/)を利用して、Pokemon GOみたいなサイトを個人でつくってみました!

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

#つくったもの
https://azukiazusa1.github.io/Pokomeon-get-Adventure/

###エリア選択画面
<img width="1427" alt="スクリーンショット 2020-02-01 12.32.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496565/c06d373c-403a-7176-f251-0bb0541071e2.png">

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

###ポケモン選択画面
<img width="858" alt="スクリーンショット 2020-02-01 13.02.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496565/82f2cc87-aeba-c403-c512-66abfd7ed9dc.png">


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

###戦闘画面
<img width="858" alt="スクリーンショット 2020-02-01 13.06.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496565/f9c2ffba-ea12-96cc-d241-cffd4c0532d5.png">

###捕獲後
<img width="1256" alt="スクリーンショット 2020-02-01 13.07.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496565/22ca303f-f8c4-a499-9cdf-46f0140bad33.png">

###ポケモンずかん
<img width="1421" alt="スクリーンショット 2020-02-01 13.08.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496565/05254dfc-6b9d-f9f3-25bc-9e7ccc280bf0.png">

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

###さいきんつかまえたポケモン
<img width="828" alt="スクリーンショット 2020-02-01 13.10.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496565/14c0eb1a-994f-cd58-f827-a0b0c86869e1.png">

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

#つかった技術
[Vue CLI](https://cli.vuejs.org/)で開発をおこない、GitHub Pagesで公開しました！
GitHub Pagesは静的サイトならコミットするだけで簡単にリリースができます。

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

- [Vue router](https://router.vuejs.org/ja/)
- [Vuex](https://vuex.vuejs.org/ja/)
- [axios](https://github.com/axios/axios)
- [vuex-persistedstate](https://github.com/robinvdvleuten/vuex-persistedstate)
- [vuex-router-sync](https://github.com/vuejs/vuex-router-sync)
- [vue-fontawesome](https://github.com/FortAwesome/vue-fontawesome)
- [vue-flash-message](https://github.com/keen-on-design/vue-flash-message)
- [vue-burger-menu](https://github.com/mbj36/vue-burger-menu)
- [anime.js](https://animejs.com/)
- [moment.js](https://momentjs.com/)

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

###Vuex
VuexはVue.ｊｓの公式のデータフローの状態管理ライブラリです。
後ほど今回のデータフローについて説明しますが、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](https://fontawesome.com/)(多数のwebアイコンを使用できるサービス)をVue.jsで使用するためのライブラリです。

###vue-flash-message
Vue.jsでフラッシュメッセージが使用できるライブラリです。
![flash.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496565/6e9de9af-5124-678d-e934-9b60cec5efd0.gif)

###vue-burger-menu
Vue.jsでハンバーガーメニューを使用できるライブラリです。
![burger.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496565/cf886f15-d49a-d7b3-290f-5ffec1feeb62.gif)


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

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



#APIの仕様確認
今回のWebアプリは[Poke API](https://pokeapi.co/)を利用してつくられています。
Poke APIはRESTfulなAPIです。教育用のAPIのようですが、データ量も豊富でドキュメントもかなりしっかりしています。

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

<img width="1430" alt="スクリーンショット 2020-02-01 21.28.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496565/e614e09e-ce41-ad4b-eaa6-4a9468427118.png">

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

- `pal-park-area`
- `pokemon`、
- `pokemon-species`

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

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

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

```js
{
  "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/を叩いてみましょう。

```js

"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/を叩きましょう。

```js
--- 省略 ---
 "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.](https://github.com/fanzeyi/pokemon.json)

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

![vuex.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496565/dc1540f4-6e87-f7cd-3f9c-bff0465f8a41.png)

##ポケモンをゲットしてから図鑑に登録するまでのデータフロー
さきほどの図をもとに設計します。

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

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

```js

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('通信エラーが発生しました。')
    }
  }
}
```

####Mutaｔions
APIから、`pokemon`と`pokemon-species`を取得して、それぞれのミューテーションにcommitします。

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

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

```js
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
`registPokedex`と`registRecentryGet`の２つのミューテーションにcommitします。
`registPokedex`は、図鑑にデータを登録する処理なので、すでに登録済みのデータだった場合にはなにもしません。
対して`registRecentryGet`はポケモンの重複があっても登録します。

```js
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`ファイルを作成して、以下の設定を記述します。

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

こちらを参考にさせていただきました：[Vue CLI で作った Vue Router 利用プロジェクトを GitHub Pages で公開する方法](https://r17n.page/2019/10/22/github-pages-with-vue-router/)

GitHubのレポジトリからsettingを選択して、GitHub Pagesの設定のsourceを、master branch/docs folderを選択します。
<img width="748" alt="スクリーンショット 2020-02-01 22.36.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496565/2dd7f197-7dc9-336f-3f22-3b32f42525df.png">

さらに、Vue Routerのhistoryモードを使用している場合には、更に設定が必要です。
[公式](https://router.vuejs.org/ja/)にもあるように、サーバーの適切な設定をすることが必要です。

>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](http://www.backalleycoder.com/2016/05/13/sghpa-the-single-page-app-hack-for-github-pages/)

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

```html

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

更に、`index.html`に以下を追記します。Vueが読み込まれる前に記述しないといけないようなので、<head>タグ内に記述しておくと万全です。

```html

<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](https://jp.vuejs.org/)
[Vue Router](https://router.vuejs.org/ja/)
[Vuex](https://vuex.vuejs.org/ja/)
[JavaScript - MDN - Mozilla](https://developer.mozilla.org/ja/)
[Vue.js入門 基礎から実践アプリケーション開発まで](https://www.amazon.co.jp/Vue-js%E5%85%A5%E9%96%80-%E5%9F%BA%E7%A4%8E%E3%81%8B%E3%82%89%E5%AE%9F%E8%B7%B5%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E9%96%8B%E7%99%BA%E3%81%BE%E3%81%A7-%E5%B7%9D%E5%8F%A3-%E5%92%8C%E4%B9%9F/dp/4297100916/ref=asc_df_4297100916/?tag=jpgo-22&linkCode=df0&hvadid=295678107984&hvpos=&hvnetw=g&hvrand=10843417956773270776&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009329&hvtargid=aud-828501354917:pla-529052961492&psc=1&th=1&psc=1)
[Vue.jsとDjango REST FrameworkでSPAなWebをやる時の勘どころ - HATEOASと非同期処理（の触り）](https://shinyorke.hatenablog.com/entry/vue-django-hateoas)
[モーダルウィンドウ | 基礎から学ぶ Vue.js](https://cr-vue.mio3io.com/examples/modal.html)
[JavaScriptで重みを付けてランダムに出現させる。(重み付き乱択)](https://www.namakedame.work/js-randomized-algorithm/)
[Vue CLI で作った Vue Router 利用プロジェクトを GitHub Pages で公開する方法](https://r17n.page/2019/10/22/github-pages-with-vue-router/)