Nuxt.jsの基本2.5（軽く経験するNuxt.js）

 ## 入る前に
この記事はこの前の記事[Nuxt.jsの基本2（Nuxt.jsについて）
](https://qiita.com/KIMSeongJoo/items/b4ae6778d78e08b9d5c6)の続きになります。
必ず読む必要はないですが、参考までになります。

この記事では、Nuxt.jsの使い方について本当に何も知らない立場から接近してみようと思います。
本当に基本的な内容なので、もうかなり使い慣れてる方にはあんま役にならないと思います。

## この記事の目標
Nuxt.jsが基本的に提供してる下記の機能について、軽く説明と、例文を書きながら見について行きたいと思います。

- Nuxt.jsのRouting機能
- Nuxt.jsのStore機能
- Nuxt.jsのLayoutsについて
- Nuxt.jsのMiddlewareについて
- Nuxt.jsのPluginsについて
- Nuxt.jsでの非同期データ取得について
- Nuxt.jsのContextについて
- Nuxt.jsのMetaタグにつにて

## Nuxt.js Routing
```javascript
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router
```

上記のコードはVue.jsのルータ設定ファイルであります。

Vue.jsでは、`/src/views` ディレクトリにコンポーネントを作成し、このルータ設定ファイル`(/src/router/index.js)` にそのコンポーネントのルータ設定を一度に入力する必要がありました。
しかし、Nuxt.jsではそうする必要はないです！

Nuxt.js Routerの簡単な例を見てみましょう。
ルータには大きく二つの種類があって、「パラメータを受け取るDynamic Route」と「パラメータを受けないBasic Route」であります。

### Basic Route
Nuxt.jsは、`/pages` フォルダにファイルを作って入れることで自動でRoutingされます。
`/pages` に `HelloWord.vue` ページを作成してます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76353/f3e1f544-d823-8085-6371-555ac98fab2f.png)
ページ生成後localhost:3000/helloworldに接続してみればHelloWorld.vueページが出力されることを確認できます。
どうすれば可能ですか？
それはページを作成した直ちに.nuxt/router.jsでは自動設定が行われるからです。

```javascript
import Vue from 'vue'
import Router from 'vue-router'
import { normalizeURL, decode } from 'ufo'
import { interopDefault } from './utils'
import scrollBehavior from './router.scrollBehavior.js'

const _a017cf3c = () => interopDefault(import('../pages/HelloWorld.vue' /* webpackChunkName: "pages/HelloWorld" */))
const _7f5caf40 = () => interopDefault(import('../pages/index.vue' /* webpackChunkName: "pages/index" */))

const emptyFn = () => {}

Vue.use(Router)

export const routerOptions = {
  mode: 'history',
  base: '/',
  linkActiveClass: 'nuxt-link-active',
  linkExactActiveClass: 'nuxt-link-exact-active',
  scrollBehavior,

  routes: [{
    path: "/HelloWorld",
    component: _a017cf3c,
    name: "HelloWorld"
  }, {
    path: "/",
    component: _7f5caf40,
    name: "index"
  }],

  fallback: false
}

export function createRouter (ssrContext, config) {
  const base = (config._app && config._app.basePath) || routerOptions.base
  const router = new Router({ ...routerOptions, base  })

  // TODO: remove in Nuxt 3
  const originalPush = router.push
  router.push = function push (location, onComplete = emptyFn, onAbort) {
    return originalPush.call(this, location, onComplete, onAbort)
  }

  const resolve = router.resolve.bind(router)
  router.resolve = (to, current, append) => {
    if (typeof to === 'string') {
      to = normalizeURL(to)
    }
    return resolve(to, current, append)
  }

  return router
}
// .nuxt/router.js
```
このフィアルを別途触ってもないのに、勝手にRouterとして設定されてます！

#### Basic Route - ネストされたルーティング
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76353/25f837ff-0c56-076c-c5bb-98b22c029ca6.png)
ネストされたルーティングとは、上の図のように、ネストされたコンポーネントのルータ設定を指します。
パスを見て、どのコンポーネントが入れ子になっているかを判断できます。
上の図のような例を実装してみましょう。

<div align="center">
<img width="200px" alt="images" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76353/539f5d9c-079c-b13f-e885-9a405f799ef3.png" title="abc" >
/pages

</div>

```vue
<template>
  <div>
    <div class="container">
      Container です。
      <nuxt-child />
    </div>
  </div>
</template>

<script>
export default {

}
</script>

<style scoped>
.container {
  width: 300px;
  height: 300px;
  background-color: pink;
  z-index: 0;
}
</style>
// pages/Container.vue
```
```vue
<template>
  <div>
    <div class="content1">
      Content1 です
    </div>
  </div>
</template>

<script>
export default {

}
</script>

<style scoped>
.content1 {
  background-color: lightblue;
  margin: 30px;
  width: 200px;
  height: 200px;
}
</style>
// pages/Container/Container1.vue
```
```vue
<template>
  <div>
    <div class="content1">
      Content2 です。
    </div>
  </div>
</template>

<script>
export default {

}
</script>

<style scoped>
.content1 {
  background-color: lightblue;
  margin: 30px;
  width: 200px;
  height: 200px;
}
</style>
// pages/Container/Container2.vue
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76353/3450a8b1-a756-86e5-4eaa-e19b5f406761.png" width="200px">
localhost:3000/Container
<br/>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76353/5e39b74b-1d8d-3328-5540-23b5e5134691.png" width="300px">
localhost:3000/Container/Container1
<br/>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76353/2ec8c4d1-4d9f-3182-1fe3-cfeade06698f.png" width="300px">
localhost:3000/Container/Container2

ネストされたルーティングも自動的に設定されました。
親コンポーネントと同じ名前のディレクトリを作成した後
そのディレクトリ内にサブコンポーネントを作成しました。
親コンポーネント内に子コンポーネントが入る場所に <nuxt-child /> 定義してくれれば終了。
Nuxt.jsが自動的にルーティングを完了します。

```javascript
...
  routes: [{
    path: "/Container",
    component: _130fa16f,
    name: "Container",
    children: [{
      path: "Container1",
      component: _183fdd80,
      name: "Container-Container1"
    }, {
      path: "Container2",
      component: _1823ae7e,
      name: "Container-Container2"
    }]
  }, {
    path: "/HelloWorld",
    component: _a017cf3c,
    name: "HelloWorld"
  }, {
    path: "/",
    component: _7f5caf40,
    name: "index"
  }],
...
// .nuxt/router.js
```

ルーティング設定ファイルを見ると、親ルータコンテナに子属性が追加され、子ルータが定義されていることを確認できます。

#### Dynamic Route

アンダーバー+ファイル名の形式でファイルを生成すると、そのファイル名のパラメータを受け取ります。

<img width="300px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76353/2e004752-7ebb-1415-87d0-fe9f845f0282.png">
/pages

```vue
<template>
  <div>
    <div>Editing Product {{ $route.params.product_id }}</div>
  </div>
</template>

<script>
export default {
}
</script>

<style scoped>

</style>
// pages/Products/edit/_product_id.vue
```
product_idというルートのパラメータを受け取り、画面に振りかけるコードです。
自動コンパイル後に.nuxt/router.jsを確認してみると、以下のようなコードが追加されているはずです。

```javascript
{
    path: "/Products/edit/:product_id?",
    component: _b6428b84,
    name: "Products-edit-product_id"
  }
```
product_idをパラメータとして受け取るという設定が追加されました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76353/ce0f68f8-00cc-0e81-0b18-03c6f7a88e2d.png" width="300px">
localhost:3000/products/edit/1 

localhost:3000/products/edit/1 でアクセスすれば、パスの params を受けて画面に散らすことを確認できます。

パラメータの検証も可能です。
```javascript
validate({ params, query, store }) {
  return true // if the params are valid
  return false // will stop Nuxt.js to render the route and display the error page
}
```
Nuxt.jsが提供するvalidate()メソッドを使用すると、パラメータの検証が可能です。
validate()は、新しいルータにナビゲートする前に呼び出します。
Nuxt context オブジェクトを`argument`として持ちます。 [詳細はこちら](https://nuxtjs.org/docs/components-glossary/validate/)

```vue
<template>
  <div>
    <div>Editing Product {{ $route.params.product_id }}</div>
  </div>
</template>

<script>
export default {     // この部分追加↓
  validate({ params }) {
    // must be a number
    return /^\d+$/.test(params.product_id)
  }
}
</script>

<style scoped>

</style>
// pages/Products/edit/_product_id.vue
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76353/cfefe1cd-01b4-6e3b-ad18-10cc3c88556f.png" width="300px">
localhost:3000/Products/edit/test の結果

product_idを数字だけ受け取るように検証し、localhost:3000/products/edit/testにアクセスすると上記のように`404エラー`を出します。

## Nuxt.js Store
Nuxtはpagesディレクトリと同様の構造でストアを構築できます。
Storeは大きく`Classic`モードと`Module`モードを提供します。
(Store機能が必要ない場合は、Storeフォルダを消去するだけです)

### Classic
Classicモードは`store`ディレクトリに`index.js`を必要とします。 (Vuex設定ファイル)この`index.js`はVuexインスタンスを返す`export`関数を実装するだけです。
これにより、通常のVueプロジェクトでVuexを使用するのと同じように、必要に応じてストアを作成できます。
```javascript
import Vuex from 'vuex'

const createStore = () => {
 return new Vuex.Store({
   state: ...,
   mutations: ...,
   actions: ...
 })
}

export default createStore
```

### Module
Moduleモードは`store`ディレクトリに`index.js`などを必要とします。（必ずしも`index.js`である必要はありません。目的のモジュールの名前空間で指定）
しかし、モジュールモードでは、このファイルからルート`state/mutations/actions`を`export`するだけです。

```javascript
export const state = () => ({})
```

たとえば、 `store` ディレクトリ内に `product.js` を作成し、以下のように実装すると、
`product`という名前空間が生成され、モジュール化ができます。

```javascript
export const state = () => ({
 _id: 0,
 title: 'Unknown',
 price: 0
})

export const actions = {
 load ({ commit }) {
   setTimeout(
     commit,
     1000,
     'update',
     { _id: 1, title: 'Product', price: 99.99 }
   )
 }
}

export const mutations = {
 update (state, product) {
   Object.assign(state, product)
 }
}
// store/product.js
```

```vue
<template>
 <div>
   <h1>View Product {{ product._id }}</h1>
   <p>{{ product.title }}</p>
   <p>Price: {{ product.price }}</p>
 </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
 created () {
   this.$store.dispatch('product/load')
 },
 computed: {
   ...mapState(['product'])
 }
}
</script>
// pages/product/view.vue
```
productモジュールのid、title、priceを画面に表示させるコードです。
約1秒後にloadアクションを経てstateが更新されることが確認できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76353/20cd1d26-52f4-a28d-17d0-fd6db3115349.png" width="300px">
localhost:3000/product/view


:::note warn
**注意**  
上記の例では、loadという偽のAPIを実装して使用しました。
ここでの問題は、1秒後にstateが更新されるまで0、Unknownなどの初期化データが見えるということです。
おそらく、実際のAPIでもresponseが完了するまで初期値が表示されます。
私たちはこの問題を解決するためにNuxtが提供するfetchを使うことができます。
詳細は以下で説明します！
:::

## Nuxt.js Layouts
Nuxt.jsはnavbar、footer、headerなどのレイアウト機能を提供します。
一般的なVueプロジェクトのApp.vueに似た機能です。

```vue
<template>
  <div>
    <h1>Admin Layout</h1>
    <nuxt />
  </div>
</template>

<script>
export default {

}
</script>

<style scoped>

</style>
// layouts/admin-layout.vue
```

```vue
<template>
  <div>
    admin page
  </div>
</template>

<script>
export default {
  layout: 'admin-layout'
}
</script>

<style scoped>

</style>
// pages/admin.vue
```

`layouts`ディレクトリに`admin-layout.vue`というレイアウトファイルを作成し、
`pages`ディレクトリに`admin.vue`というページを作成しました。
`admin.vue`に`layout：'admin-layout'`と定義するだけで、そのファイルは`admin-layout.vue`の`<nuxt />`要素に配置できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76353/d0a0f5cd-961d-803d-8c24-97ac76b5226f.png" width="300px">
localhost:3000/admin

重要なことは、レイアウトファイルに必ず`<nuxt />`要素を含める必要があるということです。

## Nuxt.js Middleware
`middleware`は`pages`または`layouts`をレンダリングする前に実行できる機能です。
ミドルウェアが解決されるまで、ユーザーに何も表示しません。
これは、Vuexリポジトリで有効なログインを確認したり、一部のパラメータを検証したりするために使用できます。 (`validate()` メソッドの代わりに)

## Nuxt.js Plugins
pluginsディレクトリを使用すると、アプリケーションが作成される前にVueプラグインを登録できます。
これにより、Vueインスタンスのアプリ全体で共有し、すべてのコンポーネントからアクセスできます。

たとえば、vue-notificationsプラグインを注入するとします。

- npm i vue-notifications (ここまでVueと同じ)
- pluginsディレクトリにvue-notifications.jsファイルを掘り下げて入力します。
- import Vue from 'vue' import VueNotifications from 'vue-notifications' Vue.use(VueNotifications)
- nuxt.config.js に plugins: ['~/plugins/vue-notifications'] を入力
終わりです。

## Nuxt.js 非同期データ取得
Nuxt.jsは、コンポーネントの`mounted` Hookからデータを取得するように、クライアント側でデータをロードするための既存のVueパターンをサポートします。

しかし、Universal Appを実装している場合は、サーバー側のレンダリング中にデータをレンダリングできるようにNuxt.js関連のフック（`fetch`、`asyncData`）を書く必要があります。

#### asyncData
- コンポーネントデータを設定する前に非同期処理ができるようにします。
- コンポーネントをロードする前に呼び出されます。
- `pages`コンポーネントでのみ使用可能です。
- `Context`オブジェクトを最初の引数として受け取り、それを使用して一部のデータを取得してコンポーネントデータとして返すことができます。
- 戻り値はコンポーネントの`data`とマージされます。
- コンポーネントを初期化する前に実行されるため、メソッド内で`this`を介してコンポーネントインスタンスにアクセスできません。

```javascript
export default {
    async asyncData({ params }) {
        const { data } = await axios.get(`https://my-api/posts/${params.id}`);
        return { title: data.title };
    },
};
```

#### fetch
- ページがレンダリングされる前にデータをストアに入れるために使用されます。
- すべてのコンポーネントで利用可能です。
- コンポーネントをロードする前に呼び出されます。
- Contextオブジェクトを最初の引数として受け取り、そのデータをストアに入れることができます。
- return値はPromiseです。
- Promiseを返すと、Nuxtはレンダリング前にPromiseが終了するのを待ちます。

上のStore Moduleの例に続いて説明します。
<注意>のセクションで説明しましたが、あるApiを介してデータをインポートするとき、responseが来るまで初期値がそのまま露出されるという問題がありました。
この問題は、上記の`fetch`の特徴の6番「Promise を返すと、Nuxt はレンダリング前に `Promise`が終了するのを待つ」で解決できます。

`/store/product.js`の`load`アクションメソッドがPromiseを返すように変更し、（async/awaitを書いても問題ありません）
`/pages/products/view.vue`ファイルを`fetch`で修正してみました。

```javascript
export const state = () => ({
  _id: 0,
  title: 'Unknown',
  price: 0
})

export const actions = {
  load ({ commit }) {
    return new Promise(resolve => {
      setTimeout(() => {
        commit('update', { _id: 1, title: 'Product', price: 99.99 })
        resolve()
      }, 1000)
    })
  }
}

export const mutations = {
  update (state, product) {
    Object.assign(state, product)
  }
}
// store/product.js
```

```vue
<template>
  <div>
    <h1>View Product {{ product._id }}</h1>
    <p>{{ product.title }}</p>
    <p>Price: {{ product.price }}</p>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  fetch() {
    this.$store.dispatch('product/load')
  },
  computed: {
    ...mapState(['product'])
  }
}
</script>

<style scoped>

</style>
// pages/product/view.vue
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76353/9467be1d-d6a9-c2fb-68fb-f4cd5ade256b.png" width="300px" >
localhost:3000/product/view

私たちは`product/load`が実行されるまでレンダリングされませんが、responseが来ると画面に`product/load`の結果値が浮かぶと予想しました。

しかし、結果として、`product/load`アクションメソッドは実行されませんでした。 なぜですか？

理由は `fetch`、`asyncData`のような`Supercharged`メソッドはVueコンポーネントが生成される前に実行されるため、コンポーネント`this`を指さない。 そのため、上記の例の`this.$store`は`undefined`状態です。 コンポーネントが生成された後にはthisの使用が可能です。

それでは、`fetch`や`asyncData`を使用するときにどのように`Store`にアクセスできますか？
すぐに`Context`を使用すればよいです。

## Nuxt.js Context
Nuxtは、すべてのメソッドにContextという非常に便利なオブジェクトを含む引数を提供します。
ここにはアプリ全体で参照する必要があるすべてがあります。 つまり、Vueがコンポーネントへの参照を最初に生成するのを待つ必要はありません。
（Contextが何を持っているのかは[公式ドキュメント](https://nuxtjs.org/docs/internals-glossary/context/)を参照してください。）

上記のfetchの例では、コンテキストを構造化し、ここでStoreを抽出します。
```javascript
export default {
 fetch ({ store }) {
   return store.dispatch('product/load')
 },
 computed: {...}
}
// pages/product/view.vue
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76353/b31102e0-0f0a-7f66-8110-f55ee9ffbfa6.png" width="300px">
localhost:3000/product/view 

初期値が開かず、responseが来てからレンダリングになる様子が確認できました。

## Nuxt.js Meta タグ
Vue.jsではSEOのためのメタタグを付けるためにvue-metaなど外部ライブラリを利用しなければならなかったが、Nust.jsでは、別のライブラリを追加せずにメタデータを設定できます。 基本的にvue-metaライブラリが搭載されているからです。

#### Global 設定
```javascript
export default {
  head: {
    title: 'my website title',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      {
        hid: 'description',
        name: 'description',
        content: 'my website description'
      }
    ],
    link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
  }
}
// nuxt.config.js
```
Nuxt.js設定ファイル（nuxt.config.js）にメタデータを設定すると、アプリケーションのすべての基本タグを定義できます。
SEOの目的で基本的なタイトルと説明タグを追加したり、ビューポートを設定したり、ファビコンを追加するのに非常に便利です。
上記のコードのように設定すると、すべてのページに同じタイトルと説明が表示されます。

#### Local設定
コンポーネントァイルの`<script>`タグ内のメソッドを使用して、ページごとのメタデータを追加することもできます。
```vue
<script>
export default {
  head: {
    title: 'Home page',
    meta: [
      {
        hid: 'description',
        name: 'description',
        content: 'Home page description'
      }
    ],
  }
}
</script>

// methodでも可能
<template>
  <h1>{{ title }}</h1>
</template>
<script>
  export default {
    data() {
      return {
        title: 'Home page'
      }
    },
    head() {
      return {
        title: this.title,
        meta: [
          {
            hid: 'description',
            name: 'description',
            content: 'Home page description'
          }
        ]
      }
    }
  }
</script>
```

## まとめ
いかがでしょうか？？
これで少しは、Nuxt.jsがどんな感じで動作するのか、どんなことをすればやりたいことができるのか、などは理解できますかね？
Vue.jsを少しでも使って作業したことがある人にはこんなこと本当に大したことでもないかもしれませんが、自分みたいに、何年間ずっとサーバーや、インフラ環境のみ触った人にはそれも時間かかる部分だと思います。
自分と似てる状況で働いてる方にある程度役に立てたらそれで嬉しいです。

もう、次が最後に記事になります。
次回は、静的検査ツールの[ESLint](https://eslint.org/) と、そのLintとペアーとしてよく使われてる、[Prettier](https://prettier.io/)について軽く整理してみようと思います。