Nuxt.js・Vue.js の基本的な使い方を理解する

# 概要

Nuxt.js (Vue.js) を使って簡単な画面を実装することを通して、Nuxt.js の基本的な使い方を学びます。
今回実装する画面の仕様は以下の通りです。

- API から json データを取得する（今回は json を取得する関数で代用）
- データをテーブルに表示する
- データの件数も表示する

実際にこんな画面ができあがります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63950/591f41dd-05f6-154c-2019-0f17876b5424.png)


# 準備

## 必要なソフトウェア

事前に以下のソフトウェアをインストールしておく必要があります。

- Node.js
- Yarn
- 好みのエディタ (VSCode がおすすめ)

## Nuxt.js のプロジェクトを作成する

Nuxt.js のプロジェクトは [create-nuxt-app](https://ja.nuxtjs.org/guide/installation/#-code-create-nuxt-app-code-%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B) を使うとほんの数分で作成できます！

```
$ yarn create nuxt-app nuxt-json-to-table
```

いくつか質問されますが、以下のようにします。
詳しい説明は今回の本題から外れるので省略します。

```
? Project name nuxt-json-to-table
? Project description My super-duper Nuxt.js project
? Use a custom server framework none
? Choose features to install Linter / Formatter, Prettier
? Use a custom UI framework none
? Use a custom test framework none
? Choose rendering mode Universal
? Author name Taro Yamada
? Choose a package manager yarn
```

これで Nuxt.js のプロジェクトが作成されました！

## 開発環境を起動する

プロジェクトのディレクトリに移動し、`yarn dev` コマンドで開発環境を起動します。

```
$ cd nuxt-json-to-table
$ yarn dev
```

http://localhost:3000 を開き、以下のような画面が表示されたら起動成功です！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63950/b79c1bfd-35fb-1b09-7be1-4a9bf442625d.png)

これで開発の準備が整いました！

# 実装

早速ですが、以下のような流れで実装していきます！

- ページを作成する
- json を取得する関数を用意する
- ボタンを押すと json を取得してそのまま表示する
- データをテーブルに表示する
- 件数を表示する
- ページを開いたときに json を取得する

各ステップごとに、まず実装（コード）を示してから、その中で使った構文などを解説するというスタイルで進めます。

## ページを作成する

まずはテーブルを表示するためのページを作成しましょう。
ページを作成するには、`pages` ディレクトリに Vue ファイルというものを作成する必要があります。

以下の内容で `pages/table.vue` を作成します。

```vue:pages/table.vue
<template>
  <div>
    <h1>table page</h1>
  </div>
</template>

<script>
export default {}
</script>
```

保存したら、http://localhost:3000/table を開いてみてください。
以下のような画面が表示されるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63950/5fbc7a9c-4fd5-c492-f08c-ed3dd1300632.png)

たったこれだけでページを作成することができました！簡単ですね！

せっかくなので、作成したページへのリンクをトップページに表示してみます。
`pages/index.vue` を以下のように編集します。

```diff:pages/index.vue
 <div class="links">
   <a href="https://nuxtjs.org/" target="_blank" class="button--green"
     >Documentation</a
   >
   <a
     href="https://github.com/nuxt/nuxt.js"
     target="_blank"
     class="button--grey"
     >GitHub</a
   >
+  <n-link to="table" class="button--grey">Table</n-link>
 </div>
```

保存したら、http://localhost:3000 を開いてみてください。
「Table」のリンクが増えているはずです！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63950/3c04968f-c424-1e24-ae8e-104d83f892d0.png)

これでページの作成は完了です！

「Vue ファイル」「`pages` ディレクトリ」「`n-link`」という見慣れないものが出てきたと思います。
以下で説明します。

### Vue ファイル

`pages/table.vue` のように、Vue ファイルには関連する HTML・JavaScript・CSS を1つのファイルにまとめて書きます。
（外部から読み込むことも可能）

- HTML は `<template>` ブロックに書く
- JavaScript は `<script>` ブロックに書く
- CSS は `<style>` ブロックに書く

HTML・JavaScript・CSS それぞれでファイルを分けることと比較して、以下のような利点があります。

- UI コンポーネントごとにコードが分離される
- 影響範囲を Vue ファイルの中に閉じ込めることができる
- 保守しやすくなる

個人的にはもう HTML・JavaScript・CSS それぞれでファイルを分けることはしなくないです...笑

ちなみに、Vue ファイルのことを [単一ファイルコンポーネント (Single File Component)](https://jp.vuejs.org/v2/guide/single-file-components.html) と言います。SFC と略されることが多いです。（スーパー○ァミコンではないです）

### Nuxt.js におけるルーティング

Nuxt.js では、`pages` ディレクトリに Vue ファイルを作成すると、そのディレクトリ構造に沿ってページが作成されます。

ルーティング - Nuxt.js
https://ja.nuxtjs.org/guide/routing

別のページへのリンクを作成するときは [`nuxt-link (n-link)`](https://ja.nuxtjs.org/api/components-nuxt-link) タグを使います。

ちなみに、ルーティングには [Vue Router](https://router.vuejs.org/ja/) を使っており、Nuxt.js を使わない場合は自分で設定する必要があります。

## json を取得する関数を用意する

続いて、json を取得する関数を用意します。

`<script>` の直下に `getJson()` 関数を定義します。

```js:table.vue
/**
 * 年齢別の推定ユーザー数を json で返す。件数はランダム
 */
function getJson() {
  const ages = Array.from(
    new Set(
      new Array(parseInt(Math.random() * 100))
        .fill(null)
        .map(() => parseInt(Math.random() * 100))
    )
  )
  return ages.map(age => ({
    age,
    users: Math.random() * 10000
  }))
}
```

この関数は以下のように年齢とその推定ユーザー数を返します。

```json
[
    {
        "age": 32,
        "users": 66.6471422182191
    },
    {
        "age": 14,
        "users": 6426.058219829258
    },
    ...
]
```

実装の説明は今回の本題から外れるので省略しますmm

## ボタンを押すと json を取得してそのまま表示する

定義した `getJson()` を使って、json を画面に表示してみましょう。

まずは、「ボタン」と「json を表示する場所」を `pages/table.vue` の `<template>` に記述します。

```diff:pages/table.vue
 <template>
   <div>
     <h1>table page</h1>
+    <button @click="fetch">fetch</button>
+    <p>{{ items }}</p>
   </div>
 </template>
```

ボタンには `@click="fetch"` を付与していて、クリックされたら `fetch()` メソッドを実行するようにしています。

次に、ボタンを押した時の処理を `pages/table.vue` の `export default {}` に記述していきます。

```js:pages/table.vue
export default {
  data: () => ({
    items: []
  }),

  methods: {
    fetch() {
      this.items = getJson()
    }
  }
}
```

保存したら、http://localhost:3000/table を開いてみてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63950/80562f86-2ec7-835a-169f-e55f22e573e2.png)

fetch ボタンをクリックすると json が表示されました！

`data` とか `methods` とか急に色々でてきましたね。順番に紹介していきます。

### [`data`](https://jp.vuejs.org/v2/guide/instance.html#%E3%83%87%E3%83%BC%E3%82%BF%E3%81%A8%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89)

- コンポーネントの状態をここに書きます。
- 画面に表示すべきデータ、表示の有無などを保持するのに使います。
- [関数にする必要があります。](https://jp.vuejs.org/v2/guide/components.html#data-%E3%81%AF%E9%96%A2%E6%95%B0%E3%81%A7%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%9B%E3%82%93)

### [`{{ }}` (ムスタッシュ構文)](https://jp.vuejs.org/v2/guide/syntax.html#%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88)

- `data` を DOM にバインディングします。
- `data` の更新を検出して自動的に画面を更新してくれます。

### `methods`

- `<template>` 内で利用できるメソッドをここに記述します。
- `<template>` 内で使う必要がなければ Vue インスタンス外に関数定義しても構わないです。

### [`@click` (`v-on:click`)](https://jp.vuejs.org/v2/guide/events.html#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%81%AE%E8%B3%BC%E8%AA%AD)

- クリックイベントを登録するために使います。
- 上記の `methods` で定義したメソッドも指定できます。
- `@input` は input イベント、`@change` は change イベントなど、基本的な DOM のイベントハンドラをこの構文で登録できます。

## データをテーブルに表示する

では、取得した json からテーブルを描画してみます。
`pages/table.vue` で、json をそのまま表示させていた部分を table に置き換えます。

```diff:pages/table.vue
-<p>{{ items }}</p>
+<table border="1">
+  <tr>
+    <th>年齢</th>
+    <th>推定ユーザー数</th>
+  </tr>
+  <tr v-for="item in items" :key="item.age">
+    <td>{{ item.age }}</td>
+    <td>{{ item.users }}</td>
+  </tr>
+</table>
```

保存したら、http://localhost:3000/table を開いてみてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63950/ec98b7d3-e299-14a0-a09e-1345d71c993a.png)

テーブルが表示されました！

このステップでは、`v-for` という構文が出てきました。

### [v-for](https://jp.vuejs.org/v2/guide/list.html#v-for-%E3%81%A7%E9%85%8D%E5%88%97%E3%81%AB%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0%E3%81%99%E3%82%8B)

- 配列から同じ要素を繰り返し表示するために使います
- [繰り返される要素に `key` を指定することが推奨](https://jp.vuejs.org/v2/guide/list.html#%E7%8A%B6%E6%85%8B%E3%81%AE%E7%B6%AD%E6%8C%81)されています。
- [書き方によっては配列の変化を検出できないことがある](https://jp.vuejs.org/v2/guide/list.html#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85)ので注意が必要です。

## 件数を表示する

続いて、取得したデータの件数を表示してみましょう。

`pages/table.vue` の `<template>` に件数を表示する部分を追加します。

```diff:pages/table.vue
 <div>
   <h1>table page</h1>
   <button @click="fetch">fetch</button>
+  {{ length }}件 fetch しました
   <table border="1">
     <tr>
       <th>年齢</th>
```

`length` が必要なので、`<script>` 部分で以下のように定義します。

```diff:pages/table.vue
   items: []
 }),

+computed: {
+  length() {
+    return this.items.length
+  }
+},

 methods: {
   fetch() {
     this.items = apiRequest()
   }
```

保存したら、http://localhost:3000/table を開いてみてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63950/591f41dd-05f6-154c-2019-0f17876b5424.png)

件数が表示されました！

このステップでは、件数を求めるために `computed` という構文を使いました。


### [`computed` (算出プロパティ)](https://jp.vuejs.org/v2/guide/computed.html)

- `data` の値を元に評価される値です。
- 呼び出し時に、`data` の更新を検出して再評価されます。
- [`methods` とは明確な違いがあります。](https://jp.vuejs.org/v2/guide/computed.html#%E7%AE%97%E5%87%BA%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3-vs-%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89)

## ページを開いたときに json を取得する

いよいよ最後です。ページを開いた時に自動的に json を取得するようにしてみます。
（開いた時点でデータが表示されていてほしいですよね？）

`pages/table.vue` の `<script>` に以下を追加します。

```diff:pages/table.vue
   }
 },

+created() {
+  this.fetch()
+},

 methods: {
```

保存したら、http://localhost:3000/table を開いてみてください。
fetch ボタンを押さずともテーブルにデータが表示されたはずです！

ページを開いたときに何か処理を実行したい場合は `created` を使います。 

### [created](https://jp.vuejs.org/v2/guide/instance.html#%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AB%E3%83%95%E3%83%83%E3%82%AF)

- Vue インスタンスが生成された直後に呼び出されます。
- `mounted` や `updated` など、他にも様々なタイミングで発火するライフサイクルフックがあります。
- ページを開く時に API からデータを取得する場合は [`asyncData`](https://ja.nuxtjs.org/guide/async-data) の方が適切なことが多い。

# まとめ

ここまでで、最初に説明した仕様をすべて満たすことができました！

この記事では、取得したデータをテーブルに表示するページの実装を通して、Nuxt.js (Vue.js) について以下の内容を学びました。

- SFC
- ルーティング
- data
- methods
- computed
- ムスタッシュ構文
- `@click` (イベント)
- v-for
- created (ライフサイクルフック)

これらは Nuxt.js・Vue.js のほんの一部にすぎませんが、

- Nuxt.js・Vue.js のことが少し理解できた！
- Nuxt.js・Vue.js を使ってみたくなった！

と思っていただけたら嬉しいです！

また、本文中でも Nuxt.js・Vue.js の公式ドキュメントへのリンクを入れていたのですが、Nuxt.js・Vue.js は公式ドキュメントが本当に充実しています！しかも日本語です！
そのおかげで勉強するにあたっての敷居はかなり低いので、少しでも興味を持ったらぜひ読んでみてくださいー！

# おまけ

## さらに改良してみる

今回実装したページにはまだまだ改善の余地がありますよね...！
改善案と、それを実現するために使う Nuxt.js・Vue.js の機能を紹介しておくので、ぜひチャレンジしてみてください！

- 小数点以下を四捨五入して、3桁区切りのカンマを表示する
    - [filter](https://jp.vuejs.org/v2/guide/filters.html) を使う
- fetch 中は「Loading...」と表示する
    - [v-if](https://jp.vuejs.org/v2/guide/conditional.html) を使う
- 値が大きいほどセルの背景色を濃くしてみる
    - [v-bind](https://jp.vuejs.org/v2/guide/class-and-style.html) を使う
- 表示する数値は推定ユーザー数固定ではなく、フォームから入力して指定できるようにする
    - [v-model](https://jp.vuejs.org/v2/guide/forms.html) を使う
- フォームで指定したものをクエリパラメーターとして保持する
    - [`route.query`](https://router.vuejs.org/ja/api/#%E3%83%AB%E3%83%BC%E3%83%88%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3), [watchQuery](https://ja.nuxtjs.org/api/pages-watchquery) を使う
- fetch ボタンとテーブルのコンポーネントを分離する
    - [components](https://jp.vuejs.org/v2/guide/components-registration.html#%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E7%99%BB%E9%8C%B2), [props](https://jp.vuejs.org/v2/guide/components.html#%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%9F%E5%AD%90%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%B8%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E5%8F%97%E3%81%91%E6%B8%A1%E3%81%97), [slot](https://jp.vuejs.org/v2/guide/components-slots.html) を使う
- 四捨五入の filter を共通化する
    - [mixin](https://jp.vuejs.org/v2/guide/mixins.html) or [plugin](https://ja.nuxtjs.org/guide/plugins#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%88%E3%82%84-context-%E3%81%AB%E6%B3%A8%E5%85%A5%E3%81%99%E3%82%8B) を使う

## 実装サンプル

今回実装したコードは以下のリポジトリに置いてあります！
「おまけ」についてもすべて実装済みです。
https://github.com/shun91/nuxt-json-to-table