Vue.js入門 ―最速で作るシンプルなWebアプリケーション

第5回 シングルページアプリケーションを拡充する

この記事を読むのに必要な時間:およそ 5 分

フック関数

Vue Routerでは,ページ遷移が実行される前後に処理を追加できるフック関数の仕組みが提供されています。以下,グローバルのフック関数ルート単位のフック関数コンポーネント内のフック関数それぞれ3つのパターンを紹介します。

グローバルのフック関数

全てのページ遷移に対して設定できるフック関数です。router.beforeEachに関数をセットすると,ページ遷移が起こる直前にその関数が実行されます。

引数のtoとfromには,現在遷移しようとしているルーティングの遷移先ルートと遷移元ルートの情報が入っています。このtoとfromに格納されるルートは,マッチしたルートのパスやコンポーネントの情報を持っています。ルートオブジェクトの詳細については公式ドキュメントをご参照ください。

router.beforeEach((to, from, next) => {
  // ユーザー一覧ページへアクセスした時に/topへリダイレクトする例
  if (to.path === '/users') {
    next('/top')
  } else {
    // 引数なしでnextを呼び出すと通常通りの遷移が行われる
    next()
  }
})

上記の例では,ユーザー一覧ページへアクセスした時に/topへリダイレクトする方法を紹介するためにnext('/top')と記述しています。問題なく通常のルーティングとして遷移したい場合は,next()と引数なしで呼び出してください。このフック関数内でnextを呼び出さないと,延々と遷移が終わらなくなる点に注意してください。

ルート単位のフック関数

もし全ての遷移に対してではなく特定のルート単位でフックを追加したい場合は,Vue Router初期化時のマッピング定義時に指定できます。beforeEnterを記述することで,ルーティング前のフックを追加することができます。

var router = new VueRouter({
  routes: [
    {
      path: '/users',
      component: UserList,
      beforeEnter: (to, from, next) => {
        // /users?redirect=true でアクセスされた時だけtopにリダイレクトするフック関数を追加
        if (to.query.redirect === 'true') {
          next('/top')
        } else {
          next()
        }
      }
    }
  ]
})

コンポーネント内のフック関数

コンポーネント内でフック関数を足したいときには,コンポーネント内で定義するフック関数が利用できます。beforeRouteEnterを使ってデータを取得する例を紹介します。本稿のデータの取得で解説した方法とは異なる実装例として,コンポーネントが表示される前にデータの取得を行い,完了したタイミングでページ遷移が行われます。

var UserList = {
  template: '#user-list',

  data: function () {
    return {
      users: function () { return [] },
      error: null
    }
  },

  // ページ遷移が行われ,このコンポーネントが初期化される前に呼び出される
  beforeRouteEnter: function (to, from, next) {
    getUsers((function (err, users) {
      if (err) {
        this.error = err.toString()
      } else {
        // nextに渡すcallbackでコンポーネントにアクセスすることができます
        next(function (vm) {
          vm.users = users
        })
      }
    }).bind(this))
  }
}

上記のコードの動作サンプルはこちらです。この例ではコンポーネントが表示されるタイミングのフック関数であるbeforeRouteEnterを利用しました。他にも,次の遷移の発生によりコンポーネントが去っていく際のフック関数beforeRouteLeaveも利用可能です。beforeRouteLeaveを使うと,たとえば保存していない変更がある時にページを去る際に,confirmを表示するなどの実装も可能になります。

簡易認証付きSPAの実装

ルート単位のフック関数を使用して,ダミーデータを利用した簡易認証付きSPAを実装してみましょう。

本稿で作成したUserListのコンポーネントにユーザー詳細ページへのリンクを追加します。そのユーザー詳細ページへのアクセスにはログインが必要になるような実装を紹介します。

まずはダミーデータ(emailアドレス:vue@example.comパスワード:vueを使って,認証するLoginモジュールを作成します。

var Auth = {
  login: function (email, pass, cb) {
    // ダミーデータを使った擬似ログイン
    setTimeout(function () {
      if (email === 'vue@example.com' && pass === 'vue') {
        // ログイン成功時はローカルストレージにtokenを保存する
        localStorage.token = Math.random().toString(36).substring(7)
        if (cb) { cb(true) }
      } else {
        if (cb) { cb(false) }
      }
    }, 0)
  },

  logout: function () {
    delete localStorage.token
  },

  loggedIn: function () {
    // ローカルストレージにtokenがあればログイン状態とみなす
    return !!localStorage.token
  }
}

次に,ユーザーの詳細ページへ遷移しようとした時に,認証ページを表示するようにルート単位のフック関数を定義します。

  var router = new VueRouter({
    routes: [
      {
        path: '/top',
        component: {
          template: '<div>トップページです。</div>'
        }
      },
      {
        path: '/users',
        component: UserList
      },
      {
        path: '/users/:id',
        component: UserDetail,
        beforeEnter: function (to, from, next) {
          // 認証されていない状態でアクセスした時はloginページに遷移する
          if (!Auth.loggedIn()) {
            next({
              path: '/login',
              query: { redirect: to.fullPath }
            })
          } else {
            // 認証済みであればそのままユーザー詳細ページへ進む
            next()
          }
        }
      },
      {
        path: '/login',
        component: Login
      },
      {
        path: '/logout',
        beforeEnter: function (to, from, next) {
          Auth.logout()
          next('/')
        }
      }
    ]
  })

次にログインコンポーネントを作成しましょう。認証が失敗した場合は,エラーメッセージを表示するようにします。

<script type="x-template" id="login">
  <div>
    <h2>Login</h2>
    <p v-if="$route.query.redirect">
      ログインしてください
    </p>
    <form @submit.prevent="login">
      <label><input v-model="email" placeholder="email"></label>
      <label><input v-model="pass" placeholder="password" type="password"></label><br>
      <button type="submit">ログイン</button>
      <p v-if="error" class="error">ログインに失敗しました</p>
    </form>
  </div>
</script>
var Login = {
  template: '#login',
  data: function () {
    return {
      email: 'vue@example.com',
      pass: '',
      error: false
    }
  },
  methods: {
    login: function () {
      Auth.login(this.email, this.pass, (function (loggedIn) {
        if (!loggedIn) {
          this.error = true
        } else {
          // redirectパラメーターが付いている場合はそのパスに遷移
          this.$router.replace(this.$route.query.redirect || '/')
        }
      }).bind(this))
    }
  }
}

上記の実装で詳細ページにアクセスしようとした時に以下のような認証ページが表示されます。

その他,詳細ページのコンポーネント実装やログアウトの機能なども含めた今回のSPA動作サンプルは,こちらで確認できます。

実際にユーザー一覧ページからユーザーの名前をクリックすると,ログイン画面が表示されます。

図2 認証画面

図2 認証画面

メールアドレスにvue@example.comパスワードにvueを入力すると認証が成功し,ユーザーの詳細ページへ遷移します。

図3 ユーザー詳細ページ

図3 ユーザー詳細ページ

まとめ

いかがでしたでしょうか,前回と今回でVue Routerの基本から少し高度な機能を使ったSPA実装について紹介しました。

Vue.jsとVue Routerを使うと一見複雑そうに見えるWebアプリケーションが思ったより簡単に実装できることを実感できたのではないでしょうか。次回最終回はVue.jsをマスターするための高度な機能の解説をする予定です。乞うご期待。

著者プロフィール

手島拓也(てじまたくや)

新卒で入社した日本IBM研究所にて検索・テキスト解析ソフトウェア製品の開発に関わったのち,LINE株式会社にて数多くのLINEプラットフォーム開発を担当。その後,株式会社Indieの共同創業者&CTOとしてサービス開発全般を担当。

GitHub:tejitak
Twitter:@tejitak