Nuxt.js + TypeScript + Vuetify でWebアプリ開発環境を構築する

## 1. はじめに

社内で `Nuxt.js` + `TypeScript` + `Vuetify` の構成で、SPAを開発しました。
その際に開発効率が高くて良いと感じたので、環境構築や開発の方法をご紹介します。

良いなと思ったことは下記の3点です。

* Nuxt.jsを使うことで環境構築が簡単に行える
* TypeScriptを使うことで型の恩恵を受けながら開発を行える
* Vuetifyを使うことでマテリアルデザインのUIを手軽に扱える

今回は下の画像のようなSPAを作成するところをゴールとします。
現在の時刻から、今年が何％進んでいるかをプログレスバーで表示します。

|![Screen Shot 2019-03-14 at 22.28.51.png](https://qiita-image-store.s3.amazonaws.com/0/315364/0737bc00-b7c4-44d4-c31c-9e908d12ce97.png)|
|:-:|

※この記事ではSSRは取り扱いません。ご了承ください。

## 2. 技術紹介

この記事で取り扱う下記の言語やフレームワークについて概要をご紹介します。

1. Nuxt.js
2. TypeScript
3. Vuetify

### 2.1. Nuxt.js

`Nuxt.js` は、 `Vue.js` のアプリケーションを構築するためのフレームワークです。
`Vue.js` で開発する際に必要になることが多い、下記のようなフレームワークを含んでいます。

1. Vue-Router
2. Vuex
3. Vue Server Renderer

本来であれば上記のフレームワークが必要かどうか検討し、必要なものを組み合わせる設定をしなければいけないのですが、 `Nuxt.js` を使用すればこれらの面倒から開放されます。

### 2.2. TypeScript

`TypeScript` はマイクロソフトによって開発された言語で、 `JavaScript` に静的型付けやクラスベースのオブジェクト指向といった機能を追加したような言語です。

### 2.3. Vuetify

`Vue.js` をベースに開発されたUIフレームワークで、下記のような特徴があります。

1. マテリアルデザインを手軽に扱える
2. ドキュメントやサンプルのソースコードが豊富

特に後者の「サンプルが豊富」である点が、他のフレームワークに比べていいところだと思います。

## 3. 構築手順

上記でご紹介した技術が使えるような環境を構築する手順をご紹介します。
下記の4つの手順に分けてご紹介します。

1. テンプレートのプロジェクトから始める
2. UIのソースコードを削除する
3. Vuetifyを導入する
4. 自分のアプリを実装する

手順1と2でプロジェクトの準備、手順3と4でアプリの実装を進めます。

### 3.1. テンプレートのプロジェクトから始める

`Nuxt.js` では環境構築を簡単にするめにいくつかのテンプレートが準備されています。
今回は `TypeScript` 用のテンプレートを使用します。

まずは `vue-cli` のインストールです。
この記事ではグローバルにインストールします。
コマンドを実行してバージョン番号が表示されたら問題なしです。

```
$ npm install -g @vue/cli

$ vue --version
> 3.5.1

$ npm install -g @vue/cli-init

$ vue init --version
> 3.5.1
```

&nbsp;  

`vue-cli` の準備が整ったら、いよいよテンプレートからプロジェクトを作成します。
下記ページを参考に進めます。
https://github.com/nuxt-community/typescript-template

```
$ vue init nuxt-community/typescript-template my-project
```

プロジェクト名などを聞かれますので適宜答えてください。
プロジェクトが作成されたら早速ローカルで動かしてみましょう。

```
$ cd my-project

$ npm install

$ npm run dev
```

&nbsp;  

ブラウザで localhost:3000 に接続してページが表示されたら成功です。

|![Screen Shot 2019-03-14 at 21.23.40.png](https://qiita-image-store.s3.amazonaws.com/0/315364/b0f048f7-6c41-7d5e-d490-62323846b2dc.png)|
|:-:|

### 3.2. UIのソースコードを削除する

次に、UIのソースコードを削除して自分のアプリを作る準備をします。
現在はロボットの顔が表示されているので、これが表示されないようにします。
言語が `TypeScript` なので、エディタはVisual Studio Codeがおすすめです。
https://code.visualstudio.com/

この記事の対象外になっているSSRの記述部分を削除します。
`my-project/store/index.ts` の `nuxtServerInit()` を削除してください。
その際に不要になった `import` もあれば削除してください。

```ts
// 中略

export const actions: ActionTree<RootState, RootState> = {

  // ここから下を削除

  async nuxtServerInit({ commit }, context) {
    let people: Person[] = []

    people = context.isStatic ?
      localRandomData :
      await context.app.$axios.$get("./random-data.json")

    commit("setPeople", people.slice(0, 10))
  }

  // ここから上を削除

}
```

コードを削除して保存すると、自動で再ビルドが走り、すぐにブラウザで変更を確認できます。
変更が反映されていないと思ったら念の為リロードしてみてください。
`Nuxt TypeScript Template` の文字だけがページに表示されていたら成功です。

![Screen Shot 2019-03-14 at 21.37.59.png](https://qiita-image-store.s3.amazonaws.com/0/315364/d7e72e12-d05d-6eb3-92d1-42986411f0f6.png)

### 3.3. Vuetifyを導入する

次にUIフレームワーク `Vuetify` を導入していきます。
まずは `Ctrl + c` で `npm run dev` を停止させ、下記コマンドで `Vuetify` を依存性に追加します。

```
$ npm install @nuxtjs/vuetify
```

次に設定ファイルに `@nuxtjs/vuetify` を追記します。

```ts
export default {
  // 中略
  modules: [
    "@nuxtjs/axios",
    // 以下の行を追記
    "@nuxtjs/vuetify"
  ],
}
```

なんとこれだけで `Vuetify` が使えるようになっています。
導入が簡単なフレームワークは嬉しいです。
早速 `Vuetify` を使ってみるために、ページを編集していきます。
`my-project/pages/index.vue` を編集します。

`<template>` 内を下記のように書き換えます。
少し行数が多いですが、ほとんど `Vuetify` のドキュメントやサンプルのページに載っているものなので安心してください。
https://vuetifyjs.com/ja/components/navigation-drawers

```html
<template>
  <v-app>
    <v-navigation-drawer app permanent>
      <v-toolbar flat>
        <v-list>
          <v-list-tile>
            <v-list-tile-title class="title">
              Sample App
            </v-list-tile-title>
          </v-list-tile>
        </v-list>
      </v-toolbar>

      <v-divider></v-divider>

      <v-list dense class="pt-0">
        <v-list-tile
          v-for="item in items"
          :key="item.title"
        >
          <v-list-tile-action>
            <v-icon>{{ item.icon }}</v-icon>
          </v-list-tile-action>

          <v-list-tile-content>
            <v-list-tile-title>{{ item.title }}</v-list-tile-title>
          </v-list-tile-content>
        </v-list-tile>
      </v-list>
    </v-navigation-drawer>

    <v-content>
      <h1 class="header">Nuxt TypeScript Starter</h1>
    </v-content>
  </v-app>
</template>
```

`<v-xxx>` のように、 `v` から始まる要素は `Vuetify` で準備されている要素です。
これらを組み合わせることで簡単にマテリアルデザインのUIを構築することができます。 

保存すると、これだけでナビゲーションドロアーが実装できました。
手軽にUIを構築できるので `Vuetify` はいいですね。

|![Screen Shot 2019-03-14 at 21.53.21.png](https://qiita-image-store.s3.amazonaws.com/0/315364/474bcf51-085c-3f98-1467-29f6b50d1ef9.png)|
|:-:|
### 3.4. 自分のアプリを実装する

最後に目標の、今年のプログレスバーを実装したいと思います。

まずはナビゲーションドロアーのメニューを作ります。
今回はVueコンポーネントにメニューに関するデータをもたせたいと思います。
`my-project/pages/index.vue` を下記のように編集します。

```ts
// 中略

@Component({
  components: {}
})
export default class extends Vue {

  items: Object[] = []

  mounted() {
    this.items = [
      {
        title: 'Progress',
        icon: 'timer'
      }
    ]
  }
}
```

ビューコンポーネントに `items` というフィールドを持たせ、 `mounted()` が実行された時点で初期化しています。

`mounted()` は特別なメソッドで、ビューコンポーネントがページにロードされた時に実行されるライフサイクルです。

実は 3.3 で書いた `<template>` の中に、この `items` を参照する記述があります。
`v-for="item in items"` や `{{ item.title }}` がこれに該当します。
この様に「データや処理に関する記述」と「デザインに関する記述」を分けて書くことができるのが、 `Vue.js` や `Nuxt.js`のいいところだと思います。

次にプログレスバーを準備し、UIを作っていきます。
下記のように `<v-content>` 内を書き換えます。

```html
// 中略

    <v-content class="bar-container">
      <p class="title">
        This year progress
      </p>
      <v-progress-linear
        v-model="yearProgress"
      ></v-progress-linear>
      <p class="percentage">
        {{ `${this.yearProgress.toFixed(6)}%` }}
      </p>
    </v-content>
```

`<v-progress-linear>` という要素を書くだけでプログレスバーを実現できます。
さすが `Vuetify`、便利です。

このままだと `yearProgress` が定義されていないというエラーになってしまいます。
ビューコンポーネントにフィールドを追加し、初期化するようにしましょう。

```ts
@Component({
  components: {
  }
})
export default class extends Vue {
  items: Object[] = []
  yearProgress: number = 20

  mounted() {
    this.items = [
      {
        title: 'Progress',
        icon: 'timer'
      }
    ]
  }
}
```

最後にCSSを整えましょう。

```html
// 中略

<style scoped>

.title {
  font-family: 'Roboto', sans-serif;
  font-size: 20px;
  text-align: center;
}

.percentage {
  font-family: 'Roboto', sans-serif;
  font-size: 18px;
  text-align: center;
}

.bar-container {
  margin: 40px;
}

</style>
```

ここまでの記述で20%のプログレスバーができました。

|![Screen Shot 2019-03-14 at 22.20.56.png](https://qiita-image-store.s3.amazonaws.com/0/315364/546e0908-66f9-fc1f-f5fe-ad6e2e2d4ceb.png)|
|:-:|

あとはこの `yearProgress` を動的に計算していくだけです。
ビューコンポーネントを下記のように書き換えます。

```ts
@Component({
  components: {}
})
export default class extends Vue {

  items: Object[] = []
  
  yearProgress: number = 0

  mounted() {
    this.items = [
      {
        title: 'Progress',
        icon: 'timer'
      }
    ]

    this.start()
  }

  start() {
    setInterval(() => {
      const date: Date = new Date()
      const numDaysOfMonth: number = this.calculateDaysOfMonth(date.getFullYear(), date.getMonth())

      const seconds: number = date.getSeconds() + date.getMilliseconds() / 1000.0
      const minutes: number = date.getMinutes() + seconds / 60.0
      const hours: number = date.getHours() + minutes / 60.0
      const days: number = date.getDate() + hours / 24.0
      const months: number = 1.0 * date.getMonth() + days / numDaysOfMonth
      
      this.yearProgress = 100.0 * months / 12
    }, 100)
  }

  calculateDaysOfMonth(year: number, month: number) {
    return new Date(year, month, 0).getDate()
  }
}
```

`start()` が実行されると、その後100msごとに今年の進捗を計算するように実装しました。
計算結果は `yearProgress` に格納しています。

もしこの実装中に型を間違えるような記述をすると、ビルド時にエラーとなります。
`TypeScript` を採用したことで、型の間違いを早く知ることができます。

ここまでで下記のような、今年の進捗を表示するアプリが完成しました。

|![Screen Shot 2019-03-14 at 22.28.51.png](https://qiita-image-store.s3.amazonaws.com/0/315364/667a6e22-03d3-2277-0970-901f62e9e4de.png)|
|:-:|

## 4. まとめ

この記事では `Nuxt.js`, `TypeScript`, `Vuetify` の環境構築方法と、簡単なアプリケーションの実装をご行いました。
モダンなフレームワークを取り入れることで下記のようなメリットを享受できました。

1. Nuxt.jsとそのテンプレートによる簡単な環境構築
2. TypeScriptによる静的型付けの恩恵
3. Vuetifyによる簡単でリッチなUIの組み立て

現在Web開発のフレームワークは他にもありますが、この記事を読んで `Nuxt.js`, `TypeScript`, `Vuetify` を使ってみようと思ってくださる方がいらっしゃれば幸いです。