LoginSignup
11
7

More than 3 years have passed since last update.

VueRouter の beforeEach 処理でエラーをキレイに描画したい

Last updated at Posted at 2019-08-15

問題:VueRouter の beforeEach の next() で再描画時に、元の画面のデータが残ってしまう。

vue-exception-route-failure.gif

下記の通り Vue のエラー時の routing 処理を実装したいと思い、下記の通り実装を進めました。

  • Vue-Cliの Typescript プロジェクトを作成
  • src/router.ts に routing 処理を VueRouter を使い実装する。
  • dashboard にアクセスした際に、認証の有無を確認する処理を router.beforeEach において実装する。
  • vuex store に実装した isAuthenticated:boolean が false だった場合、AuthenticationError を throw する。
  • AuthenticationError を catch した際は、vuex store に実装した、setApplicationMessage 処理を呼び出して Errorメッセージを描画する。

しかしVueRouter の beforeEach の next() で再描画時に、元の画面のデータが残ってしまうという問題が発生しました。
その解決までにいろいろと調べたところを記載します。
その時のソースは下記の通りです。

src/router.ts
import Vue from 'vue';
import VueRouter from 'vue-router';
import Login from '@/views/Login.vue';
import Dashboard from '@/views/Dashboard.vue';
import ServerError from '@/views/ServerError.vue';
import store from '@/store/store'
Vue.use(VueRouter);

const routes = [
    {path: '/', component: Login},
    {path: '/dashboard', component: Dashboard, meta:{ requiresAuth: true }},
    {path: '/ServerError',component: ServerError},
];

const router:VueRouter = new VueRouter({
    mode: 'history',
    routes
});

router.beforeEach((to, from, next)=>{
    try
    {
        if(to.matched.some(record => record.meta.requiresAuth)
          && ! (store.state.isAuthenticated)) 
        {
            console.log('認証が必要です');
            throw new AuthorizationError('認証が必要です');
        }

        store.commit('setApplicationMessage', '');
        next();
    }
    catch(error)
    {
        let path = '';
        if (error instanceof AuthorizationError) 
        {
            //ログイン画面に遷移
            path = '/';
            store.commit('setApplicationMessage', error.message);
        }
        store.commit('setRedirecting', true);

        path ? next(path)
             : next();
    } 
});


export default router;

原因:Vuejs のリソースの再利用

Vue.js アプリケーションのシステム的に、VueRouterは同じリソースの描画をなるべく行わないようにするらしく、VueRouter の beforeEach でroutingの制御をした時 たまたま意図しないデータの保持が行われてしまうようでした。

解決法:エラー時の再描画前に、必ずダミーのパスへの遷移を行い、パラメータをリセットする。

ApplicationError発生時にだけ、必ずメッセージの再描画するために、エラー時の再描画前に、必ずダミーのパスへの遷移を行い、パラメータをリセットすることで解決しました。

src/router.ts

src/router.ts
import Vue from 'vue';
import VueRouter from 'vue-router';
import Login from '@/views/Login.vue';
import Dashboard from '@/views/Dashboard.vue';
import {Profile} from '@/store/profile';
import store from '@/store/store';
import ProfileState from '@/store/states/profile-state';
import AuthorizationError from '@/errors/AuthorizationError';
import ServerError from '@/views/ServerError.vue';
import ErrorHandler from '@/errors/error-handler';

Vue.use(VueRouter);

const routes = [
    {
        path: '/',
        component: Login
    },
    {
        path: '/dashboard',
        component: Dashboard,
        meta: { requiresAuth: true }
    },
    {
      path: '/ServerError',
      component: ServerError
    },
];

const router:VueRouter = new VueRouter({
    mode: 'history',
    routes
});

router.beforeEach((to, from, next)=>{
    const redirecting:boolean = store.getters.redirecting;
    console.log('redirecting:'+redirecting)
    redirecting 
        ? store.commit('setRedirecting',false)
        : store.commit('setApplicationMessage','');

    try
    {
        if(to.matched.some(record => record.meta.requiresAuth)
        && ! (((Profile.state) as ProfileState).authenticated)) {
            console.log('認証が必要です');
            throw new AuthorizationError('認証が必要です');
        }
        if(redirecting)
        {
            next();
            return;
        }
        store.commit('reload');
        next();
    }
    catch(error)
    {
        next('__nowhere__')
        const path = ErrorHandler.handleAndGetPathOnError(error);
        store.commit('setRedirecting', true);

        path ? next(path)
             : next();
    } 
});


export default router;

また、

  • 画面の reload 処理を実装する。
    App.vue のテンプレートの divの :key を更新することで、App.vue の再描画を強制します。
    https://michaelnthiessen.com/force-re-render

  • redirect中を検知するグローバル変数を vuex store に作成して処理の重複を制御する

  • beforeEach 処理で redirect中だったら vuex seRedirectでredirect中をoffする。普通のアクセスなら、setApplicationMessageを呼び出し、メッセージを空にする。

  • this.$store.subscribe で、setApplicationMessage 処理の実行を監視して、App.vueのリロード処理を行う
    (こちらの記事を参考にさせて頂きました
    https://dev.to/viniciuskneves/watch-for-vuex-state-changes-2mgj
    )
    等の修正も併せて行いました。

src/store/store.ts

src/store/store.ts
import Vue from 'vue';
import Vuex, { StoreOptions } from 'vuex';
import { Profile } from './profile';
import RootState from './states/root-state';

Vue.use(Vuex);

const store:StoreOptions<RootState> = {
    state:{
        __applicationMessage__:'',
        __redirecting__: false,
        __reloadKey__: 0
    },
    getters:{
        getReloadKey(state:RootState):number
        {
            return state.__reloadKey__;
        },
        redirecting(state:RootState):boolean{
            return state.__redirecting__;
        },
        getApplicationMessage(state:RootState):string {
            const messsage = state.__applicationMessage__;
            return messsage;
        },
    },
    mutations:{
        setApplicationMessage(state:RootState,message:string)
        {
            state.__applicationMessage__ = message;
        },
        setRedirecting(state:RootState,occuring:boolean):void 
        {
            state.__redirecting__ = occuring;
        },
        reload(state:RootState)
        {
            state.__reloadKey__++;
        }
    },
    modules: {
        Profile
    }
};

export default new Vuex.Store<RootState>(store);

src/App.vue

src/App.vue
<template>
  <div id="app" :key='reloadKey'>
    <ApplicationMessage
     v-if="show"
     :message="getApplicationMessage()"
    ></ApplicationMessage>
      {{ this.username }}
    <div class="links">
      <router-link to="/dashboard">Dashboard</router-link>
      <router-link to="/">Login</router-link>
      <router-link to="/ServerError">ServerError</router-link>
    </div>
    <img alt="Vue logo" src="./assets/logo.png">
     <router-view/>
  </div>
</template>

<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';

import { State, Action, Getter } from 'vuex-class';

import UserInterface from '@/models/user';
import VueRouter from "vue-router";
import ApplicationMessage from '@/components/ApplicationMessage.vue';

@Component({
  components:{
    ApplicationMessage
  },
})
export default class App extends Vue {
  private get user():UserInterface{
    return this.$store.state.Profile.user;
  }
  @Getter('getApplicationMessage') applicationMessage!:string;
  @Getter('getReloadKey') reloadKey!:number;
  private username:string = this.user.name;
  private getApplicationMessage():string
  {
    return this.applicationMessage;
  }
  mounted():void {
    this.$store.subscribe((mutation, state) => {
      switch(mutation.type) {
        case 'setApplicationMessage':
          this.show = false;
          const newValue = mutation.payload; 
          if(newValue === '')
          {
            this.$store.commit('reload');
            return;
          }
          this.show = true;
          this.$store.commit('reload');
          this.$store.commit('setRedirecting',false)
          break;
      }
    });
  }
  private show:boolean= false;

}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
.links *{
  padding: 0.5rem;
}
</style>

vue-exception-route-success.gif

参照

認証機能に関する VueRouter の routing については下記の記事に詳しく書いて頂いておりましたので参考にさせて頂きました。
https://qiita.com/takatama/items/05e9fbc7199cde4caf60

Vue と Vuex による認証処理の実装については、下記の記事に詳しく書いて頂いておりましたので参考にさせて頂きました。
Vue.js で簡単なログイン画面 (トークン認証) を作ってみた
https://qiita.com/sunadorinekop/items/f3486da415d3024c7ed4

TypeScriptのError処理の実装については下記の記事に詳しく記載して頂いておりましたので参考にさせて頂きました。
TypeScriptを利用した場合の例外の基本設計
https://qiita.com/kenju/items/b0554846a44d369cba7b

Vuex での Error 動作の作成については、下記の記事を参考にさせて頂きました。
https://www.hypertextcandy.com/vue-laravel-tutorial-error-handling

Vuex 内部で Axiosを使っている事例として、下記の記事を参考にさせて頂きました。
https://qiita.com/kai_kou/items/c4e449964df59d5a5fb0

JTWToken認証処理については下記の記事を参考にさせて頂きました。
https://www.webopixel.net/php/1444.html

SPAにおける全体の例外設計に関しましては下記の記事を参考にさせて頂きました。
https://www.altus5.co.jp/blog/angular/2019/03/30/angular-error-hadling-design/

ソース

以上です。

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