JSフレームワークであるVue.jsと、そのVue.jsのフレームワークであるNuxt.jsのどちらを使うべきか?といった議論は時々見かけます。
が、結局は使う場面による、という結論だと思うので、Vue(vue-cli)と、Nuxtで同じアプリケーションを作って、「始め方」や「書き方」を整理しておきたいと思います。
2019/10/16
続編として、これらをサーバ上で動かすための「Vue.js / Nuxt.jsのアプリをnginxで動かす(サブディレクトリ対応)」を書きました。
前提
題材と完成品
ポケモンのタイプ相性表
私自身がポケモンシリーズ好きということで、18種類あるタイプの相性表(同タイプ同士もある総当たり戦)を題材としました。
- ●) 効果バツグン(2倍)
- ▲) 効果いまひとつ(0.5倍)
- ×) 効果なし(0倍)
完成したアプリの見た目がこちら。(縦軸が攻撃側、横軸が防御側)
完成したアプリとリポジトリ
※リポジトリは執筆当時なので今後更新がある・・・かも。
Vue.js(vue-cli) ver.
Vue.js(vue-cli) + TypeScript ver.
Nuxt.js ver.
Nuxt.js + TypeScript ver.
Vue.js
であればvue-cli
の初期状態からviews
に1ページ足しただけ、Nuxt.js
もpages
に1ページ足しただけ、という超簡易実装です。
使用したWeb APIについて
そもそもポケモンのタイプ相性表に変動する余地はないのですが、Vue
でアプリを作るとなったらほぼ必須となるaxiosも使うべく、無理やりWeb APIを使うことにしました。
ポケモンのタイプ情報を返却するAPI
自作しました。(こちらはGithubには上げてません)
レスポンス
[
{
"id": 1,
"name": "ノーマル",
"short_name": "ノ",
"color": "b1b1b1",
"attack_relations": [
{
"attack_id": 1,
"defense_id": 1,
"effect_rate": 1.0
},
{
"attack_id": 1,
"defense_id": 2,
"effect_rate": 1.0
},
// 略
環境構築と実装
ここから上に挙げた各アプリの環境構築と実装について書いていきます。なお、node.js
はインストール済み(npm
が使える状態)という前提です。
Vue.js(vue-cli)
vue-cliインストール
グローバル(-g)にインストールします。
$ npm install -g @vue/cli
初期設定
アプリ名はvue-cli-app
で、Babel
。Router
にチェックを入れます。(Linter / Formatter
は任意で、Vuex
は今回使ってないのでなくてもOK)
$ vue create vue-cli-app
Vue CLI v3.11.0
? Please pick a preset: Manually select features
? 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
ローカル起動確認
$ cd vue-cli-app
$ npm run serve
ここまではいわゆるHello World
的な部分なので問題はないかと思います。
Axiosインストール
$ npm install axios
+ axios@0.19.0
実装(コーディング)
全部載せると長くなりそうなので、ポイントを抜粋していきます。
Type.vueのtemplate
基本は<table>
タグを使い、APIから取得するtypes
という連想配列をループしていくだけです。背景色のRGBもAPIのレスポンスに入れてます。
<template>
<div>
<table>
<tr>
<td class="none"> </td>
<th v-for="type in types" :key="type.id"
:style="{ 'background-color' : '#' + type.color }">
{{ type.short_name }}
</th>
</tr>
<tr v-for="type in types" :key="type.id">
<th :style="{ 'background-color' : '#' + type.color }">
{{ type.short_name }}
</th>
<td v-for="(relation, idx) in type.attack_relations" :key="idx"
:class="[
relation.effect_rate == 2? 'red': '',
relation.effect_rate == 0.5? 'blue': '',
relation.effect_rate == 0? 'bold': ''
]"
>
{{ toSymbol(relation.effect_rate) }}
</td>
</tr>
</table>
</div>
</template>
Type.vueのscript
ライフサイクルフックであるcreated
でAPIを叩いて、レスポンスをtypes
に入れています。(あれ、types: []
でいいんだっけ...types: {}
...?
<script>
import axios from 'axios'
export default {
name: 'type',
data: function() {
return {
types: []
}
},
created: function () {
axios.get(process.env.VUE_APP_DOMAIN + '/v1/types')
.then((response) => {
this.types = response.data;
})
},
methods: {
toSymbol(rate) {
switch(rate) {
case 2: rate = "●";
break;
case 0.5: rate = "▲";
break;
case 0: rate = "×";
break;
default: rate = "";
}
return rate;
}
},
}
</script>
ルーティング
追加した箇所だけの抜粋ですが、非常にシンプルです。
{
path: '/type',
name: 'type',
component: Type
},
環境変数ファイル(.env)
vue-cli 3.0
からはルートディレクトリに.env
ファイルを配置すると、process.env.hogehoge
として環境変数を利用できるようです。
ただし、変数の定義方法にルールがあり、VUE_APP_
から始めること、です。
VUE_APP_DOMAIN=http://localhost:8080
アプリ側からの参照方法は以下の通りです。
axios.get(process.env.VUE_APP_DOMAIN + '/v1/types')
環境別の環境変数
上記はAPIのドメインを設定していますが、ローカルでの開発時と、サーバでの運用時はAPIドメインが変わる、といった場合には以下のように設定が可能です。
VUE_APP_DOMAIN=http://localhost:8080
VUE_APP_DOMAIN=https://hogehoge.com
ローカルでnpm run serve
した時は.env.development
、サーバでnpm run build
=> npm run start
した時は.env.production
を自動でを読みにいくようです。(やってないけど起動時に設定することも可能らしい)
Vue(vue-cli) + TypeScript
vue-cliインストール
※ Vue(vue-cli)と同じ
初期設定
アプリ名はvue-cli-ts-app
で、Babel
。Router
と、今回はTypeScript
にもチェックを入れます。(Linter / Formatter
は任意で、Vuex
は今回使ってないのでなくてもOK)
$ vue create vue-cli-app
Vue CLI v3.11.0
? Please pick a preset: Manually select features
? 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
ローカル起動確認
$ cd vue-cli-ts-app
$ npm run serve
Axiosインストール
※ Vue(vue-cli)と同じ
実装(コーディング)
構成などはVue.js(vue-cli)と変わらないので、メインはscript
部分だけと言っても過言ではないです。
Type.vueのtemplate
※ Vue(vue-cli)と同じ
Type.vueのscript
ポイントはdata
のtypes
に型定義が書かれていることと、created()
とtoSymbol()
が見た目変わらないこと、でしょうか。もちろん、created
はライフサイクルフックとして機能します。
<script lang="ts">
import axios from 'axios';
import { Component, Vue } from 'vue-property-decorator';
@Component({
components: {
},
})
export default class Type extends Vue {
// data
public types: Array<{
id: number,
name: string
short_name: string,
color: string,
attack_relations: Array<{
attack_id: number,
defense_id: number,
effect_rate: number;
}>;
}> = [];
// created
public created(): void {
axios.get(process.env.VUE_APP_DOMAIN + '/v1/types')
.then((response) => {
this.types = response.data;
});
}
// methods
public toSymbol(rate: number) {
let symbol: string;
switch (rate) {
case 2: symbol = '●';
break;
case 0.5: symbol = '▲';
break;
case 0: symbol = '×';
break;
default: symbol = '';
}
return symbol;
}
}
</script>
ルーティング
※ Vue(vue-cli)と同じ
環境変数ファイル(.env)
※ Vue(vue-cli)と同じ
Nuxt.js
create-nuxt-app
npx
というものが必要らしいのですが、npm 5.2.0
からはデフォルトでバンドルされているそう。
初期設定
アプリ名はnuxt-app
で、package manager
としてNpm
を選択し。Nuxt.js modules
はAxios
を選択しておきます。
$ npx create-nuxt-app nuxt-app
create-nuxt-app v2.11.1
✨ Generating Nuxt.js project in nuxt-app
? Project name nuxt-app
? Project description My wicked Nuxt.js project
? Author name hogehoge
? Choose the package manager Npm
? Choose UI framework None
? Choose custom server framework None (Recommended)
? Choose Nuxt.js modules Axios
? Choose linting tools (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Choose test framework None
? Choose rendering mode Universal (SSR)
? Choose development tools (Press <space> to select, <a> to toggle all, <i> to invert selection)
ローカル起動確認
vue-cli
はnpm run serve
だったので微妙に違う!
$ cd nuxt-app
$ npm run dev
Axiosインストール
※create-nuxt-app
でインストール済み
実装(コーディング)
type.vueのtemplate
※Vue(vue-cli) / Vue(vue-cli) + TSと同じ
type.vueのscript
ここもほとんどvue-cli
と変わりませんが、APIのドメイン(環境変数)の部分が微妙に異なります。同じにもできるのですが、PREFIXなしでいけるのであえて変えています。(後述)
<script>
import axios from 'axios'
export default {
name: 'type',
data: function() {
return {
types: []
}
},
created: function () {
axios.get(process.env.DOMAIN + '/v1/types')
.then((response) => {
this.types = response.data;
})
},
methods: {
toSymbol(rate) {
switch(rate) {
case 2: rate = "●";
break;
case 0.5: rate = "▲";
break;
case 0: rate = "×";
break;
default: rate = "";
}
return rate;
}
},
}
</script>
ルーティング
Nuxt.js
におけるルーティングは、pages
ディレクトリの配下がそのままルーティングになるので、ファイルとしては不要です。
今回の場合、pages/type.vue
なので、/type
というパスでアクセスすることになります。
環境変数ファイル(.env)
vue-cli
では、ルートディレクトリに.env
を置くだけでしたが、Nuxt.js
はそれが使えないのでモジュールをインストールします。
dotenv
$ npm install @nuxtjs/dotenv
インストール後、モジュールを使うための設定と、環境別に読み込むための設定をnuxt.congfig.js
に入れます。
modules: [
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
[
'@nuxtjs/dotenv',
{ filename: process.env.NODE_ENV !== 'production' ? ".env.dev" : ".env.prod" }
]
],
ちなみに、特に意味はありませんが、開発用を.env.dev
、本番用を.env.pro
というファイル名にしました。(development
、production
が長いと思ったので)
Nuxt.js + TypeScript
create-nuxt-app
※ Nuxt.jsと同じ
初期設定
アプリ名はnuxt-ts-app
で、あとはNuxt.jsの時と同じです。
なお、vue-cli
の時のように、TypeScript
をプリインストールすることはできません。
TypeScript
を使うための準備
ここがメインと言ってもいいでしょう。
インストール
$ npm install --save-dev @nuxt/typescript-build
+ @nuxt/typescript-build@0.2.9
設定
buildModules: [
'@nuxt/typescript-build'
],
以下は新規ファイルです。
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"lib": [
"esnext",
"esnext.asynciterable",
"dom"
],
"esModuleInterop": true,
"allowJs": true,
"sourceMap": true,
"strict": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"~/*": [
"./*"
],
"@/*": [
"./*"
]
},
"types": [
"@types/node",
"@nuxt/types"
]
},
"exclude": [
"node_modules"
]
}
Class-basedで書きたい
上記の準備だけでTypeScript自体は使えるようですが、Class-basedで書くならいかが必要です。
$ npm install --save vue-property-decorator
先ほど作成したtsconfig.json
に一行追記します。
"experimentalDecorators": true,
ローカル起動確認
※ Nuxt.jsと同じ
Axiosインストール
※create-nuxt-app
でインストール済み
実装(コーディング)
type.vueのtemplate
※ ※Vue(vue-cli) / Vue(vue-cli) + TS / Nuxt.jsと同じ
type.vueのscript
※ vue-cli + TypeScriptと同じ
ルーティング
※ Nuxt.jsと同じ
環境変数ファイル(.env)
※ Nuxt.jsと同じ
4つを並行して作っていたから少し混乱気味だったけど、こうして書いてみると「〜〜〜と同じ」が多いので、意外と異なる箇所はシンプルなのかな、と思いました。
ここから状態管理のVuex
を使うと更に複雑になりそうですが、それはもう少し複雑なアプリになってから考えるとして、これくらいならNuxt.js
なしのVue.js
単体でも十分そうです。