はじめに
こんにちは!
最近はすっかりVue.jsにハマってしまったので、Poke APIを利用して、Pokemon GOみたいなサイトを個人でつくってみました!
Vue CLIにおける開発からリリースまでの流れは非常に簡単でわかりやすく、Vue歴3ヶ月の私でもすぐに実践できたので、参考にしてもらえると嬉しいです!
つくったもの
エリア選択画面

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

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

捕獲後

ポケモンずかん

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

さいきんつかまえたポケモンが入手順に表示されます!
つかった技術
Vue CLIで開発をおこない、GitHub Pagesで公開しました!
GitHub Pagesは静的サイトならコミットするだけで簡単にリリースができます。
さらに、Vue CLIの開発にあたって以下のライブラリを使用しました。
開発の途中から必要になってもすぐにインストールして組み込めるので素敵ですね✨
- Vue router
- Vuex
- axios
- vuex-persistedstate
- vuex-router-sync
- vue-fontawesome
- vue-flash-message
- vue-burger-menu
- anime.js
- moment.js
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でフラッシュメッセージが使用できるライブラリです。
vue-burger-menu
Vue.jsでハンバーガーメニューを使用できるライブラリです。
anime.js
JavaScriptで簡単にアニメーションをつけることができるライブラリです。
moment.js
JavaSctiptで日付のフォーマットをするライブラリです。
APIの仕様確認
今回のWebアプリはPoke APIを利用してつくられています。
Poke APIはRESTfulなAPIです。教育用のAPIのようですが、データ量も豊富でドキュメントもかなりしっかりしています。
とりあえず叩いてみましょう。
https://pokeapi.co/api/v2/

なにも指定しないで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のによる状態管理は以下の図のようになります。
ポケモンをゲットしてから図鑑に登録するまでのデータフロー
さきほどの図をもとに設計します。
ポケモン出現
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から、pokemon
とpokemon-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
registPokedex
とregistRecentryGet
の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を選択します。
さらに、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 で公開する方法