115
97

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 5 years have passed since last update.

Vue.js #1Advent Calendar 2017

Day 17

Vue-Routerのナビゲーションガードを使ってみる

Posted at

概要

Vue.js #1 Advent Calendar 17日目です。
こんにちは、@SatohJohnです。
Vue.js人気ですね。導入して良かったです。(結果論
今回は実際に実務で使った際に覚えたVue-Routerのナビゲーションガード(before)について書きます。
つらつら書いてもわかりにくかったので、今回「許可のないidでは対象のページにアクセスさせず、failページに飛ばす。」という課題を考えました。
idが直打ちなのかよとかのツッコミは入れないでください。

前提

バージョン
Vue 2.5.11
Vue-Router 3.0.1

Vue-Routerのモードはhashを想定しています。

Vue-router

Vue-RouterはVue.jsでSPAを作る際につかいます。

ナビゲーションガード

画面にルーティングされる前にパラメータのバリデーションをして、アクセスのガードできます。

イベントの起きる順番と書き方

主にガードする場所は以下の4箇所です

  1. グローバルbeforeEach
  2. ルート単位beforeEnter
  3. コンポーネント単位beforeRouteEnter
  4. コンポーネント単位beforeRouteUpdate
vue-router-test.js
import Vue from 'vue';
import Router from 'vue-router';

Vue.use(Router);

const TestComponent = { // アクセスさせない画面を持つコンポーネント
    render: function(h) {
        return h('div', `hello: ${this.id}`)
    },
    beforeRouteEnter (to, from, next) { // [3]
        console.log('component: beforeRouteEnter');
        next();
    },
    beforeRouteUpdate (to, from, next) { // [4]
        console.log('component: beforeRouteUpdate');
        next();
    },
    props: ['id']
};
const FailComponent = {
    render: h => h('div', `you cannot access`)
};

const router = new Router({
    routes: [
        {path: '/', redirect: '/test'},
        {path: '/fail', component: FailComponent},
        {path: '/:id', component: TestComponent, props: true,
        beforeEnter: (to, from, next) => { // [2]
            console.log('router: beforeEnter');
            next();
        }},
    ]
});
router.beforeEach((to, from, next) => { // [1]
    console.log('global: beforeEach');
    next();
});

new Vue({
    router,
    render: h => h('router-view'),
}).$mount((() => {
    const list = document.getElementsByTagName('div');
    if (list.length < 1) {
        const appArea = document.createElement('div');
        document.body.appendChild(appArea);
        return appArea
    }
    return list[0];
})());

初回のページ読み込み時に実行される順番としては1→2→3です。
スクリーンショット 2017-12-16 13.57.12.png

現状何もガードを書けていないので/#/satohでも/#/johnでもTestComponentの画面にたどり着けます。
それではガードを書いてみます。

各場所でガードしてみる

1. グローバルでガードする

nextを呼ぶとtoのコンポーネントに遷移します。
しかし、ここで、idが定義されていないことを考慮しなきゃいけないのは厳しい。。。(この例だと見なければ無限ループになる。
他にコンポーネントが増えたときに。。。とか考えたくないですね。

router.beforeEach((to, from, next) => { // [1]
    console.log('global: beforeEach');
    if (to.params.id === undefined || to.params.id === 'satohjohn') {
        next();
    } else {
        next({path: '/fail'});
    }
});

2. ルート単位でガードする

書き方は1.と変わりません。ただ、ここに来るときのtoはTestComponentに対するrouteオブジェクトになるためidが無いときというのを気にしなくても良いかもしれません。
ただ、propsで指定しているのにparamsを見ているのもどうなんだろうという気がします。
また、routerの定義でロジックを書きたくは無い気がしています。(個人の感想

        {path: '/:id', component: TestComponent, props: true,
        beforeEnter: (to, from, next) => { // [2]
            console.log('router: beforeEnter');
            if (to.params.id === 'satohjohn') {
                next();
            } else {
                next({path: '/fail'});
            }
        }},

3.component単位でガードする

書き方はやはり1と変わりませんが、2と同じようにcomponentが特定されているのでidだけをみてよいと思います。2と同じようにparamsみることになります。

    beforeRouteEnter (to, from, next) { // [3]
        console.log('component: beforeRouteEnter');
        if (to.params.id === 'satohjohn') {
            next();
        } else {
            next({path: '/fail'});
        }
    },

しかし、propを参照できるように変更します。

    beforeRouteEnter (to, from, next) { // [3]
        console.log('component: beforeRouteEnter');
        next(vm => {
            if (vm.id !== 'satohjohn') {
                next({path: '/fail'});
            }
        });
    },

できたかと思いますが、しかし、これはまだガードが完全ではありません。初回アクセス時のみのガードしかできていないのです。
/#/satohjohnにアクセスした時ページが表示されていますが、これを/#/satohに変更してみると、通ってしまいます。
これは、hash eventに対して、2と3が動かないためです。

4.hash changeの際にガードする。

この問題にはコンポーネントに対するbeforeRouteUpdateを利用します。
beforeRouteUpdateはコンポーネントが再利用される際に呼ばれます。逆に言えば初回には呼ばれません。
シグネチャは今までのものと同じではありますが、thisパラメータを利用し、コンポーネントの値を取得することができます。しかし、このタイミングでは、遷移する前のidしか取れないので、to.paramsから取ります。

    beforeRouteUpdate (to, from, next) {
        console.log('component: beforeRouteUpdate');
        if (to.params.id === 'satohjohn') {
            next();
        } else {
            next({path: '/fail'});
        }
    },

これによってbeforeRouteUpdateで弾かれるようになります。

スクリーンショット 2017-12-16 16.02.12.png

これでsatohjohnさんしかTestComponentにアクセスすることができなくなりました!

その他4の解決方法

$routeオブジェクトをwatchするという手があります。
こちらの場合はnextがパラメータに渡ってこないため、$router.pushする必要があります。
ただ、this.idが更新されているのでto.paramsを見なくても良いという利点があります。

    watch: {
        $route(to, from) {
            if (this.id !== 'satohjohn') {
                this.$router.push('/fail');
            }
        }
    },

まとめ

Vue-Routerのガードを使ってみました。
beforeRouteUpdateあたりは知らずにいるとハマるポイントだと思います。

今回関係はないんですが、せっかくなのでparcelで作ってみました。
webpackより導入も簡単で、検証とかする際にとてもいいですね!

115
97
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
115
97

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?