今回達成すること
今回は、ログインに失敗した際に出力するトースターをVuetifyを使って実装します。
また、今回実装するトースターは、Vuex経由でどこからでも呼び出し可能なグローバルイベントとなります。
完成イメージ
トースターコンポーネントを作成する
トースターは、「components/ui」ディレクトリ内で管理します。
まずは、コンポーネントファイルを作成します。
root $ touch front/components/ui/toaster.vue
Vuetifyのsnackbarを使用する
トースターには、Vuetifyのv-snackbar(スナックバー)
を使用します。
まずは表示させましょう。
front/components/ui/toaster.vue
<template>
<v-snackbar
v-model="snackbar"
top
text
>
トースター
<template v-slot:action="{ attrs }">
<v-btn
v-bind="attrs"
>
Close
</v-btn>
</template>
</v-snackbar>
</template>
<script>
export default {
data () {
return {
snackbar: true
}
}
}
</script>
ログインページに表示する
作成したトースターはログインページで使用します。
Nuxt.js v2.12以下の場合は、
import
文でコンポーネントをインポートしてください。
front/pages/login.vue
<template>
<bef-login-form-card #form-card-content>
<!-- 追加 -->
<!-- toaster -->
<toaster />
...
</template>
...
トースターを確認しよう
コンテナを起動して、http://localhost:8080/login にアクセスすると、ブサイクなトースターが表示されます。
このトースターは、timeout
プロパティを指定していないので、デフォルトの5000
ミリ秒が適用されています。5秒後にはうっすら消えていきます。
グローバルイベントのトースターを作成する
トースターはどの場面でも使用します。
そこで状態管理にVuexを使用し、どこからでも呼び出せるトースターを作成します。
親子関係にないコンポーネント間通信でよく使用されていた、イベントバスはVue3で削除されました。
Nuxt.jsでも非推奨になると思うので、Vuexを使ったグローバルイベントを作成します。
Vue 3.x アップデート
インスタンスから$on、$off、$onceメソッドを完全に削除しました。引用: Vue.js
トースター表示のロジック
トースターは常に表示するコンポーネントではなく、何かしらのメッセージがあったときに表示されます。
今回の場合は、ログインが失敗した時の「ユーザーが見つかりません」ですね。
ここから考えると、
- Vuexに空のメッセージを用意し、
- その値が存在する場合に、トースターを表示する。
- そして、一定時間が経過すればメッセージを削除します。
Vuexにトースターの値を用意する
トースターのカスタマイズできる値は、
- メッセージ
- カラー
- 表示時間
とします。それぞれの値を
front/store/index.js
export const state = () => ({
...
rememberRoute: {
name: 'index',
params: {}
},
// 追加
toast: {
msg: null,
color: 'error',
timeout: 4000
}
})
-
color: 'error'
... トースタには、エラーメッセージを表示することが多いので、デフォルトカラーをerror(赤)
としています。 -
timeout: 4000
... Vuetifyのsnackbar
のタイムアウトは1000ミリ秒で指定します。4000 ~ 10000の値が推奨されています。
表示したままにする場合は
-1
を指定します。
Vuexにアクションを追加する
toast
オブジェクトの値を変更するアクションとミューテーションを追加します。
front/store/index.js
...
export const mutations = {
...
setRememberRoute (state, payload) {
state.rememberRoute = payload
},
// 追加
setToast (state, payload) {
state.toast = payload
}
}
export const actions = {
...
getRememberRoute ({ commit }, route) {
...
},
// 追加
// トーストデータをセットする
getToast ({ commit }, toast) {
toast.color = toast.color || 'error'
toast.timeout = toast.timeout || 4000
commit('setToast', toast)
}
}
これでVuexの準備が整いました。
メッセージがあればトースターを出力する
コンポーネントの編集に移ります。
front/components/ui/toaster.vue
<template>
<!-- v-model="setSnackbar" へ書き換え -->
<v-snackbar
v-model="setSnackbar"
top
text
>
トースター
<template v-slot:action="{ attrs }">
<v-btn
v-bind="attrs"
>
Close
</v-btn>
</template>
</v-snackbar>
</template>
<script>
export default {
// 削除
// data () {
// return {
// snackbar: true
// }
// },
// 以下、追加
computed: {
toast () {
return this.$store.state.toast
},
setSnackbar: {
get () {
return !!this.toast.msg
},
set (val) {
return val
}
}
}
}
</script>
-
toast ()
... Vuexのステートの値をthis.toast
で扱えるようにしています。 -
setSnackbar: {}
...v-model
の値として指定しています。-
get ()
... ゲッター。ある属性の値を取得するメソッド。 -
set (newValue)
... セッター。属性に値を設定するメソッド。
-
setSnackbar
は、Vuexのメッセージを常に参照しており、
msg
に値が代入された時にtrue
を返します。- ゲッター内で
true
が返れば、v-model=true
となり、<v-snackbar>
が表示されます。 timeout
に設定した時間が経過すると、<v-snackbar>
はfalse
を返します。false
はセッターの引数val
に入っており、その値をv-model
に渡します。v-model=false
となった時、<v-snackbar>
は非表示となります。
メッセージと値を追加する
front/components/ui/toaster.vue
<template>
<!-- timeout、color追加 -->
<v-snackbar
v-model="setSnackbar"
top
text
:timeout="toast.timeout"
:color="toast.color"
>
<!-- 追加 -->
{{ toast.msg }}
<!-- トースター 削除 -->
<template v-slot:action="{ attrs }">
<v-btn
v-bind="attrs"
>
Close
</v-btn>
</template>
</v-snackbar>
</template>
...
トースターを呼び出してみる
authFailure()
から、トースターを呼び出します。
front/pages/login.vue
<script>
export default {
...
methods: {
...
// ログイン失敗
authFailure ({ response }) {
// 追加
if (response.status === 404) {
this.$store.dispatch('getToast', { msg: 'ユーザーが見つかりません😷' })
}
// 削除
// console.log(response)
}
}
}
</script>
トースターの状態を確認するために、
front/pages/login.vue
<template>
<bef-login-form-card #form-card-content>
<!-- 追加 -->
{{ $store.state.toast }}
<!-- toaster -->
<toaster />
...
</template>
...
存在しない適用なユーザーでログインしてみましょう。
トースターが出力され、Vuexのtoast
にはメッセージが入りました。
現状の問題点
トースターが出力された後、2度目のクリック時にトースターが表示されません。
これは、メッセージが変化していないため、
算出プロパティとは
算出プロパティとは、computed
内に作成される関数のことで、依存関係にある値の変更が検知されたときに再評価した値を返します。
つまり、メッセージが変わらない限りは、関数を再び実行する事はなく、以前計算された結果を即時に返します。
解決策
この解決には、トースターが消えると同時に、メッセージを初期値のnull
に戻す必要があります。
トースターは、消えるタイミングでセッターのset()
を通過します。
そのセッター内でメッセージをnull
にするメソッドを呼び出せばOKです。
front/components/ui/toaster.vue
...
<script>
export default {
computed: {
...
setSnackbar: {
get () {
return !!this.toast.msg
},
set (val) {
// 追加
// timeout: -1の場合は通過しない
this.resetToast()
return val
}
}
},
// 追加
methods: {
resetToast () {
return this.$store.dispatch('getToast', { msg: null })
}
}
}
</script>
これで何度クリックしてもトースターが出力されるようになりました。
閉じるボタンを完成させる
トースターの閉じるボタンを完成させます。
@click
イベントでresetToast()
メソッドを呼び出せばOKです。
front/components/ui/toaster.vue
<template>
...
<template v-slot:action="{ attrs }">
<!-- 追加 -->
<v-btn
v-bind="attrs"
text
:color="toast.color"
@click="resetToast"
>
Close
</v-btn>
<!-- 削除 -->
<!-- <v-btn
v-bind="attrs"
>
Close
</v-btn> -->
</template>
</v-snackbar>
</template>
...
無期限トースターに対応する
メール認証を促すような重要なトースターは、ユーザーが閉じるボタンをクリックするまで表示した方が親切です。
トースターの表示時間timeout
に-1
を渡し、消えないトースターを表示してみましょう。
front/pages/login.vue
...
<script>
export default {
...
// ログイン失敗
authFailure ({ response }) {
if (response.status === 404) {
// timeout: -1を追加
this.$store.dispatch('getToast', { msg: 'ユーザーが見つかりません😷', timeout: -1 })
}
}
}
}
</script>
このトースターは、閉じるボタンを押さない限り消えるません。
そのままページ遷移して、また戻るとどうでしょうか。あ、まだ表示されていました。
ページ遷移直前にトースターを削除する
この、ページ遷移後にも表示されたままのトースターを削除するには、beforeDestroy()
内でトースターの値をリセットします。
beforeDestroy()
は、そのページのVueインスタンス(ページ内にあるデータや関数)が破棄される直前に呼び出されます。
「ページの後処理を行う場所」としてよく使われます。
beforeDestroy()
を追加しましょう。
front/components/ui/toaster.vue
...
<script>
export default {
computed: {
...
},
// 追加
beforeDestroy () {
// ページ遷移前に削除する(-1に対応)
this.resetToast()
},
methods: {
resetToast () {
return this.$store.dispatch('getToast', { msg: null })
}
}
}
</script>
これでページ遷移後にはトースターが削除されるようになりました。
テストコードを削除する
これで実装は完了です。
{{ $store.state.toast }}
とtimeout: -1
を
削除します。
front/pages/login.vue
<template>
<bef-login-form-card #form-card-content>
<!-- {{ $store.state.toast }} 削除 -->
...
</template>
<script>
export default {
...
// ログイン失敗
authFailure ({ response }) {
if (response.status === 404) {
// 追加
this.$store.dispatch('getToast', { msg: 'ユーザーが見つかりません😷' })
// 削除
// this.$store.dispatch('getToast', { msg: 'ユーザーが見つかりません😷', timeout: -1 })
}
}
}
}
</script>
ミドルウェアにトースターを追加する
ミドルウェアの
front/middleware/authenticator.js
export default async ({ $auth, store, route, redirect }) => {
if (!$auth.isAuthenticated()) {
// 有効期限外の時
let msg = 'ログインが必要です'
if ($auth.isUserPresent()) {
// ログイン中のユーザー
msg = 'もう一度ログインしてください'
await $auth.logout()
} else {
// ログイン前ユーザー
store.dispatch('getRememberRoute', route)
}
// 追加
store.dispatch('getToast', { msg })
// 削除
// console.log(msg)
return redirect('/login')
} else if (!$auth.isUserPresent()) {
...
}
}
リダイレクト時にトースターが現れるようになりました。
コミットしよう
コミットしましょう。
root $ cd front
front $ git add -A && git commit -m "add_toaster.vue" && cd ..
root &
まとめ
今回実装したトースターは、$store.dispatch
からメッセージを送ると、どこからでも呼び出すことができます。
例えばレイアウトファイルに<toaster />
を置くことで、そのレイアウトを使用する全てのページがらトースターを呼び出すことができます。
今回はログインページだけですが、どうぞ使い回してください。
次回は?
次は、エラーページの実装です。
お疲れ様でした。(長かった...)