1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Nuxt.jsの基本2.5(軽く経験するNuxt.js)

Last updated at Posted at 2022-05-08

入る前に

この記事はこの前の記事Nuxt.jsの基本2(Nuxt.jsについて)
の続きになります。
必ず読む必要はないですが、参考までになります。

この記事では、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

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されます。
/pagesHelloWord.vue ページを作成してます。
image.png
ページ生成後localhost:3000/helloworldに接続してみればHelloWorld.vueページが出力されることを確認できます。
どうすれば可能ですか?
それはページを作成した直ちに.nuxt/router.jsでは自動設定が行われるからです。

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
ネストされたルーティングとは、上の図のように、ネストされたコンポーネントのルータ設定を指します。
パスを見て、どのコンポーネントが入れ子になっているかを判断できます。
上の図のような例を実装してみましょう。

images /pages
<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
<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
<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
localhost:3000/Container
localhost:3000/Container/Container1
localhost:3000/Container/Container2

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

...
  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

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

/pages
<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を確認してみると、以下のようなコードが追加されているはずです。

{
    path: "/Products/edit/:product_id?",
    component: _b6428b84,
    name: "Products-edit-product_id"
  }

product_idをパラメータとして受け取るという設定が追加されました。

localhost:3000/products/edit/1

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

パラメータの検証も可能です。

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として持ちます。 詳細はこちら

<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
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を使用するのと同じように、必要に応じてストアを作成できます。

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/actionsexportするだけです。

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

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

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
<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が更新されることが確認できます。

localhost:3000/product/view

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

Nuxt.js Layouts

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

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

<script>
export default {

}
</script>

<style scoped>

</style>
// layouts/admin-layout.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.vuelayout:'admin-layout'と定義するだけで、そのファイルはadmin-layout.vue<nuxt />要素に配置できます。

localhost:3000/admin

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

Nuxt.js Middleware

middlewarepagesまたは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関連のフック(fetchasyncData)を書く必要があります。

asyncData

  • コンポーネントデータを設定する前に非同期処理ができるようにします。
  • コンポーネントをロードする前に呼び出されます。
  • pagesコンポーネントでのみ使用可能です。
  • Contextオブジェクトを最初の引数として受け取り、それを使用して一部のデータを取得してコンポーネントデータとして返すことができます。
  • 戻り値はコンポーネントのdataとマージされます。
  • コンポーネントを初期化する前に実行されるため、メソッド内でthisを介してコンポーネントインスタンスにアクセスできません。
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.jsloadアクションメソッドがPromiseを返すように変更し、(async/awaitを書いても問題ありません)
/pages/products/view.vueファイルをfetchで修正してみました。

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
<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
localhost:3000/product/view

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

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

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

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

Nuxt.js Context

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

上記のfetchの例では、コンテキストを構造化し、ここでStoreを抽出します。

export default {
 fetch ({ store }) {
   return store.dispatch('product/load')
 },
 computed: {...}
}
// pages/product/view.vue
localhost:3000/product/view

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

Nuxt.js Meta タグ

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

Global 設定

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>タグ内のメソッドを使用して、ページごとのメタデータを追加することもできます。

<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 と、そのLintとペアーとしてよく使われてる、Prettierについて軽く整理してみようと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?