Nuxtでloadingを表示しつつ、同一route間での遷移時には表示させない方法

Nuxtを使用しWebサイト等を構築する際、演出意図としてアイキャッチ的な画面を挟みたい要件が発生することがあると思います。
例えばロード時だったり、ページ間遷移時等々。

ただし、動的ルーティングを構成しているとき等、同じroute間（または特定route間）での遷移ではアイキャッチを入れたくない、という場合のメモです。

## はじめに：loadingの実装
Nuxtドキュメントの「[独自のローディングコンポーネントを使う](https://ja.nuxtjs.org/docs/2.x/features/loading#独自のローディングコンポーネントを使う)」項目を参考に、サクッとcomponentを作成します。

```vue:/components/Loading.vue
// HTMLはpug使用していますが適宜読み替えてください
<template lang="pug">
  .loader(v-if="loading")
    .logo
</template>

<script>
export default {
  data: () => ({
    // ドキュメントでは初期値がfalseだが、各ページでstartを呼ばなくても表示させたいのでtrueにセット
    loading: true,
  }),
  methods: {
    start() {
      this.loading = true;
    },
    finish() {
      this.loading = false;
    }
  }
}
</script>

<style scoped>
.loader {
  position: fixed;
  left: 0;
  top: 0;
  height: 100%;
  width: 100%;
  z-index: 9000;
  display: flex;
  justify-content: center;
  align-items: center;
  background: #000;
}
.logo {
  width: 400px;
  height: 300px;
  background: url(/img/loader-logo.png) 0 0 no-repeat;
}
</style>
```

そして`nuxt.config.js`に上記componentを記載してあげ……

```js:/nuxt.config.js
export default {
  loading: '~/components/Loading.vue',
};
```

各ページcomponent内のmountedに下記のように追記。

ページによってtimeout時間を変えたかったので、今回はこのように記述していますが、
上記loading component内の、startメソッドにsetTimeoutを書いてあげてもいいかも。

```vue:/pages/index.vue
<script>
export default {
  mounted () {
    this.$nextTick(() => {
      // 時間経過したらloadingを非表示にする
      setTimeout(() => {
        this.$nuxt.$loading.finish();
      }, 2000);
    });
  },
};
```

ここまでできたら、初回アクセス時・各ページ遷移時ともloading画面が表示されると思います。
loadingを表示させたいだけであれば、ここで終了でOK。

## 一部ページ間の遷移時にはloadingを表示させたくないとき
ここからが本題。

例えば、下記のようにディレクトリを構成し、`/member`というパスで人物一覧を表示させつつ、
人物詳細を表示させるモーダルを`/member/lisa`のようなパスで出したいとします。

`/pages/member/_name.vue`内で、
`this.$nuxt.$route.params.name`に値がセットされているときにモーダルを表示させる、という組み方です。

```
pages/
├ index.vue
├ about.vue
└ member/
  └ _name.vue
```

このとき、前項でloadingを表示させるよう設定したままの状態ですと、
見た目的にはページ遷移が行われていないにも拘わらず、モーダル開閉だけでもloadingが表示されてしまいます。
（routingが発生し、loadingのstartメソッドが呼ばれるので）

流石にこの状態では使い勝手が良くないです。
この場合だけは例外的にloadingを表示させないようにしましょう。

### routingのhookを使う
上記のようにroutingが発生するのであれば、routing時にloadingを表示させるかどうかの分岐を行えばよいでしょう。
そこで、pluginを作成して、hookを追加します。

```js:/plugins/routerOptions.js
export default ({ store, app }) => {
  app.router.beforeEach((to, from, next) => {
    // to.name / from.name にcomponent名が入っている。
    // これを用いて、同一componentのときはloadingをdisabledにする
    if (to.name === from.name) {
      // Vuex storeにcommitを行う（後述）
      store.commit('setLoadingEnable', false);
    }
    next();
  });
};
```

`nuxt.config.js`に上記pluginを登録します。

```js:/nuxt.config.js
export default {
  loading   : '~/components/Loading.vue',
  plugins: [
    '@/plugins/routerOption',
  ],
};
```

そして、loadingを表示するか否かのフラグは、Vuexに持たせます。

```js:/store/index.js
export const state = () => {
  return {
    isLoadingEnabled: true,
  };
};
export const getters = {
  isLoadingEnabled (state) {
    return state.isLoadingEnabled;
  },
};
export const mutations = {
  setLoadingEnable (state, flag) {
    state.isLoadingEnabled = flag;
  },
};
```

そして、loadingコンポーネント内で上記フラグを読み、startメソッドを修正。

```vue:/components/Loading.vue
<template lang="pug">
  .loader(v-if="loading")
    .logo
</template>

<script>
export default {
  data: () => ({
    loading: true
  }),
  methods: {
    start () {
      // LoadingがEnabledかどうかStoreに問い合わせ、
      // Enabledのときだけ表示する。
      if (this.$store.getters.isLoadingEnabled === true) {
        this.loading = true;
      }
      // Loading表示フラグをtrueにリセットする
      this.$store.commit('setLoadingEnable', true);
    },
    finish() {
      this.loading = false;
    }
  }
}
</script>
```

## まとめ
ざっくりこんな感じで、望む動作が実装できました。

上記の他にも

- 動的ルーティングを複数設置したいが一部route配下だけloadingを表示させたくない
- 特定のページではloadingを表示させたくない

等の要件があった場合は、
`/plugins/routerOptions.js`内の分岐条件を修正してあげればよいと思います。

### 参考
[How to disable Nuxt's progress bar for specific pages? - DEV Community]
(https://dev.to/farnabaz/how-to-disable-nuxt-s-progress-bar-for-specific-pages-27a6)
[Nuxt.jsでページ遷移ごとに共通の処理を実行する方法 - Qiita](https://qiita.com/tobita0000/items/2478ca8159c861752988)