【Vue】ログイン→ローディング画面→ルーティング【詰まったこと】

# 概要
vueとfirebaseを用いてログインを実装。

1. ログイン
2. ルーティング 

ログインが確認できたら特定のページにルーティング。
その際、ユーザーをセットするまでに時間がかかりルーティングに__待ち__が生じる。
この__待ち時間にローディング画面__を入れたい。

## ライブラリ
* firebase
* vuex
* vue-router
* vue-loading

※それぞれ細かい解説は省く。

## ログイン
ログインは__Googleの認証__を使用。
認証画面に自動で飛ぶため、ログインユーザーを選択。
※vuexより抜粋

```js
// vuex/actions
login() {
    const provider = new firebase.auth.GoogleAuthProvider();
    // 自動でグーグルの認証画面
    firebase.auth().signInWithRedirect(provider);
  },
```

firebaseの__[onAuthStateChanged](https://firebase.google.com/docs/auth/web/manage-users?hl=ja)__メソッドで現在ログインしているユーザーを取得。
ログインユーザーを__state(vuex)__にセット。

```js
// App.vue
created() {
    // ログインログアウトを検知
    // ログイン時とログアウト時にユーザーオブジェクトが入る
    firebase.auth().onAuthStateChanged(user => {
      if (user) {
　　　　　//ユーザーをセット
        this.setLoginUser(user);
        // ログアウト時
      } else {
　　　　　//セットしたユーザーを削除
        this.deleteLoginUser();
      }
    });
  },
```

一応セット用のコード

```js
// vuex/actions
setLoginUser({ commit }, user) {
    //mutationsでstateのlogin_userでuserをセット
    commit("setLoginUser", user);
  },
```

## ルーティング
ユーザーがログインしたのち__特定のページへ__ルーティング。
先ほどのコードに少し加えるだけ。


```js
// App.vue
created() {
     // ログインログアウトを検知
    // ログイン時とログアウト時にユーザーオブジェクトが入る
    firebase.auth().onAuthStateChanged(user => {
      if (user) {
        this.setLoginUser(user);
        // ルーティング
        if (this.$router.currentRoute.name === "home") {
          this.$router.push({ name: "input" });
        }
        // ログアウトした時
      } else {
        this.deleteLoginUser();
      }
    });
  },
```

この処理を実行した時inputへページ遷移はできるものの__ユーザーのセットに時間がかかり__ルーティングまでに__待ち__が生じる。

## ローディング画面
__vue-loading__を使用。
導入方法などは[【Vue.js】ローディング画面の実装方法（サンプルコード付き）](https://qiita.com/terufumi1122/items/214d82f74370ef377aa2)を参照。

```js
// vuex/state
const state = {
  loading: false
};
```
__loading__の値によってローディング画面をトグル。

```vue:(App.vue)
<template>
  <div id="app">
    <Loading v-show="loading"></Loading>
    <div class="container" v-show="!loading">
      <Header></Header>
      <div class="content">
        <router-view></router-view>
      </div>
    </div>
  </div>
</template>

<script>
import { mapState } from "vuex";

export default {
 computed(){
   ...mapState("loading", ["loading"])
 }
</script>
```

あとはactionsでloadingの値をトグルするだけだが、__どこで実装__すればいいかわからなかった。
色々試した結果行き着いたのが__ナビゲーションガード__によるルーティング処理。

### 試したこと
loadingの初期値はfalseなため、loadingを発動させるにはtrueにする必要がある。
したがって__ルーティングする前__にloadingの値を変えれば良い。
userオブジェクトの確認後__セットされるタイミング__と考えた。
ここで__詰まった__ため__動かなかったコード__を先に紹介。


```js
// App.vue
created() {
    // ログインログアウトを検知
    // ログイン時とログアウト時にユーザーオブジェクトが入る
    firebase.auth().onAuthStateChanged(user => {
      if (user) {
　　　　　//ユーザーをセット
        this.setLoginUser(user);
        // actionsによりloadingの値を変更
        this.setLoading(true);
        // ルーティング
        if (this.$router.currentRoute.name === "home") {
          this.$router.push({ name: "input" });
        }
        // ログアウト時
      } else {
　　　　　//セットしたユーザーを削除
        this.deleteLoginUser();
      }
    });
  },
```

次にloadingの値をfalseにしてloading画面を終わらせる必要がある。
こちらも試行錯誤したが、最終的になんてことなかったので__正解のコード__を紹介。

```js
// input.vue
// loading画面を無効
  beforeRouteEnter(to, from, next) {
    next(vm => {
      vm.setLoading(false);
    });
  },
```

こちらの２セットで動くと考えたがどうやらローディング画面が__一瞬__で終わってしまう。
動きとしては,
1. ログインユーザーを選択
2. homeコンポーネントの画面で固まる
3. __一瞬ローディング__が入ってinputコンポーネントの画面に移る

## 結果
なぜこのような動きになってしまうのか、あくまで推測だが__userオブジェクトの処理に時間がかかっていたから__である。
この処理が実行された後loadingの値を変更していたため、本来潰したかった__待ちの時間が埋まらない__結果に。

したがってuserオブジェクトを処理する前にloadingの値を変更する必要がある。
次に試してみてとりあえず__動いたコード__

```js
// App.vue
created() {
    // actionsによりloadingの値を変更
    this.setLoading(true);
    // ログインログアウトを検知
    // ログイン時とログアウト時にユーザーオブジェクトが入る
    firebase.auth().onAuthStateChanged(user => {
      if (user) {
　　　　　//ユーザーをセット
        this.setLoginUser(user);
        // ルーティング
        if (this.$router.currentRoute.name === "home") {
          this.$router.push({ name: "input" });
        }
        // ログアウト時
      } else {
　　　　　//セットしたユーザーを削除
        this.deleteLoginUser();
      }
    });
  },
```

userが入ってくる前にトリガーしているため、確かに思い通りに動く。
しかし一つだけ__大きな問題__がある。

### 初期描画
ログインユーザーが存在しない状態でアプリを開いたとする。
するとログインできないままloadingがtrueになってしまい、__無限ローディング__が始まってしまう。

## 解決策
なんとかゴリ押しで解決できた。

```js
// App.vue
created() {
    // actionsによりloadingの値を変更
    this.setLoading(true);
    setTimeout(() => {
      this.setLoading(false);
    }, 2100);
    // ログインログアウトを検知
    // ログイン時とログアウト時にユーザーオブジェクトが入る
    firebase.auth().onAuthStateChanged(user => {
      if (user) {
　　　　　//ユーザーをセット
        this.setLoginUser(user);
        // ルーティング
        if (this.$router.currentRoute.name === "home") {
          this.$router.push({ name: "input" });
        }
        // ログアウト時
      } else {
　　　　　//セットしたユーザーを削除
        this.deleteLoginUser();
      }
    });
  },
```

初期描画の段階でローディングが入るものの、2.1秒後にloadingをfalseにする処理を入れることによってローディングを止めることができた。
あまりに__パワープレイでダサい__ため気に入ってないが、私にはこれが__限界__である。
他にいい案があれば教えていただきたい。

## 気付き
__nextの処理__

```js
// input.vue
// loading画面を無効
  beforeRouteEnter(to, from, next) {
    next(vm => {
      vm.setLoading(false);
    });
  },
```

>この beforeRouteEnter ガードは this へのアクセスはできないです。なぜならば、ナビゲーションが確立する前にガードが呼び出されるからです。したがって、新しく入ってくるコンポーネントはまだ作られていないです。

>しかしながら、 next にコールバックを渡すことでインスタンスにアクセスすることができます。このコールバックはナビゲーションが確立した時に呼ばれ、コンポーネントインスタンスはそのコールバックの引数として渡されます。

>>[vue-router公式より](https://router.vuejs.org/ja/guide/advanced/navigation-guards.html#%E3%83%AB%E3%83%BC%E3%83%88%E5%8D%98%E4%BD%8D%E3%82%AC%E3%83%BC%E3%83%89)

書いてるままだが、beforeRouteEnterを使った場合でもvmを使うことによってインスタンスにアクセスできる。

以上。