はじめに
本稿は前回のシングルページアプリケーションの基礎を作成するの続編です。Vue Routerを使用して、
データの取得
SPAではページ遷移をした際に、created
とwatch
を使って実装するのが一般的です。
前回の記事のサンプルコードでは、
ナビゲーションのHTMLのテンプレートは前回の記事のものと変わりません。
<div id="app">
<router-link to="/top">トップページ</router-link>
<router-link to="/users">ユーザー一覧ページ</router-link>
<router-view></router-view>
</div>
/users
にアクセスした時のユーザー一覧用のコンポーネントが、template
プロパティにセットするのではなく、
<script type="x-template" id="user-list">
<div>
<div class="loading" v-if="loading">ロード中...</div>
<div v-if="error" class="error">
{{ error }}
</div>
<!-- usersがロードされたら各ユーザーの名前を表示する -->
<div v-for="user in users" :key="user.id">
<h2>{{ user.name }}</h2>
</div>
</div>
</script>
上記のテンプレートHTMLは、
次は、
// 擬似的にAPI経由で情報を取得したようにする
var getUsers = function (callback) {
setTimeout(function () {
callback(null, [
{
id: '001',
name: 'Takuya Tejima'
},
{
id: '002',
name: 'Yohei Noda'
}
])
}, 1000)
}
var UserList = {
// HTML上のscriptタグのidを指定する
template: '#user-list',
data: function () {
return {
loading: false,
// 初期値の空配列を定義
users: function () { return [] },
error: null
}
},
// 初期化時にデータを取得する
created: function () {
this.fetchData()
},
// ルーティングが変更された時に再度データを取得するために$routeの変更をwatchする
watch: {
'$route': 'fetchData'
},
methods: {
fetchData: function () {
this.loading = true
// 取得したデータの結果をusersに格納する
getUsers((function (err, users) {
this.loading = false
if (err) {
this.error = err.toString()
} else {
this.users = users
}
}).bind(this))
}
}
}
これでコンポーネントの実装は完了です。次に、/users
へのアクセスとUserListコンポーネントをマッピングします。この部分は前回の内容と変わりません。
var router = new VueRouter({
routes: [
{
path: '/top',
component: {
template: '<div>トップページです。</div>'
}
},
{
path: '/users',
component: UserList
}
]
})
var app = new Vue({
router: router
}).$mount('#app')
実行すると、

コードサンプルはこちらで確認できます。
フック関数
Vue Routerでは、グローバルのフック関数
、ルート単位のフック関数
、コンポーネント内のフック関数
それぞれ3つのパターンを紹介します。
グローバルのフック関数
全てのページ遷移に対して設定できるフック関数です。router.
に関数をセットすると、
引数のtoとfromには、
router.beforeEach((to, from, next) => {
// ユーザー一覧ページへアクセスした時に/topへリダイレクトする例
if (to.path === '/users') {
next('/top')
} else {
// 引数なしでnextを呼び出すと通常通りの遷移が行われる
next()
}
})
上記の例では、next('/top')
と記述しています。問題なく通常のルーティングとして遷移したい場合は、next()
と引数なしで呼び出してください。このフック関数内でnextを呼び出さないと、
ルート単位のフック関数
もし全ての遷移に対してではなく特定のルート単位でフックを追加したい場合は、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
を使うと、
簡易認証付きSPAの実装
ルート単位のフック関数を使用して、
本稿で作成したUserListのコンポーネントにユーザー詳細ページへのリンクを追加します。そのユーザー詳細ページへのアクセスにはログインが必要になるような実装を紹介します。
まずはダミーデータvue@example.
、vue
)
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))
}
}
}
上記の実装で詳細ページにアクセスしようとした時に以下のような認証ページが表示されます。
その他、
実際にユーザー一覧ページからユーザーの名前をクリックすると、

メールアドレスにvue@example.
、vue
を入力すると認証が成功し、

まとめ
いかがでしたでしょうか、
Vue.