ログイン前とログイン後でヘッダーの表示を変化させたい場合。
例えば、ヘッダー右に
・ログイン前はログインボタン
・ログイン後はログインユーザー名
を表示したいとする。
しかし、ログイン後にヘッダーの情報だけなぜか反映されず
リロードをしないとヘッダーにユーザー情報が表示されないことがある。
ヘッダーにユーザー名が表示されない
↓やりたいこと(ログイン後)
↓実際(ログイン後)
ログインしているにも関わらず、ヘッダーがユーザー名ではなくログインボタンのまま。
ちなみに、ページをリロード(更新・リフレッシュ・再読み込み・F5)すると、ちゃんと表示される。
ていうかページにはユーザー情報がちゃんと表示されてるのにヘッダーだけ表示できてないってどういうことだってばよ?
原因と対処法
ヘッダーコンポーネントが変数の変化に気づいていない
vuexのstateの監視みたいなことをしないといけない。
結論だけ言うとヘッダーコンポーネントをこんな感じにする。
//Header.vue <template> <header> <div> <b-navbar toggleable="lg" type="dark" variant="dark"> <b-navbar-brand href="#">NavBar</b-navbar-brand> <b-navbar-toggle target="nav-collapse"></b-navbar-toggle> <b-collapse id="nav-collapse" is-nav> <b-navbar-nav> <b-nav-item class="active" to="/">Home</b-nav-item> <b-nav-item to="/about">About</b-nav-item> <b-nav-item to="/stocks/create">Post</b-nav-item> <b-nav-item to="/stocks">Archive</b-nav-item> <b-nav-item to="/login">Login</b-nav-item> <b-nav-item to="/register">Register</b-nav-item> </b-navbar-nav> <b-navbar-nav class="ml-auto"> <b-nav-item-dropdown right v-if="isLoggedIn"> <template #button-content> <em>{{ userName }}</em> </template> <b-dropdown-item href="#">Profile</b-dropdown-item> <b-dropdown-item href="#">Sign Out</b-dropdown-item> </b-nav-item-dropdown> <button v-if="!(userName)">ログイン</button> </b-navbar-nav> </b-collapse> </b-navbar> </div> </header> </template> <script> export default { data() { return { userName: null, email: "", isLoggedIn: false, }; }, mounted() { this.$store.watch( (state, getters) => getters.getUserName, (newValue, oldValue) => { console.log('username changed! %s => %s', oldValue, newValue) this.isLoggedIn=true this.userName = newValue } ) this.userName = localStorage.userName //これがないとリロードしたときに名前が消える }, computed: {}, methods: {}, }; </script>
これだけ言われても意味わからんと思うから解説。
前提条件
- Vuexがすでに利用できる環境
- バックエンドの認証システムがすでに完成してる(本例ではLaravelのsanctumを利用)
親コンポーネントはこんな感じ。
分かってるわ!って人は飛ばして。
//App.vue <template> <div> <Header /> <div class="container"> <router-view /> </div> <Footer /> </div> </template> <script> import Header from '../layout/Header' import Footer from '../layout/Footer' export default { components: { Header, Footer, }, } </script>
ここにヘッダーやら、ページやらのコンポーネントが格納されていくイメージ。
ログインフォーム
//Login.vueのメソッド部分 onSubmit() { axios.post('/api/login', this.form) .then(response => { const userInfo = { name: response.data.user.name, email: response.data.user.email, token: response.data.token, } this.$store.commit("checkLogin", userInfo) //vuexに渡す this.$router.push('/home'); }) }
ログインページのメソッド部分だけ書くとこんな感じ。
this.$store.commit("checkLogin", userInfo)でログインに成功したユーザーの情報(メールアドレス・パスワード・トークン)をvuexに渡している。
ログイン処理ののAPIに関しては割愛。
vuex
//\\プロジェクトディレクトリ\resources\js\store\index.js import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const store = new Vuex.Store({ state: { name: null, email: null, token: null, }, getters: { getUserName(state) { return state.name } //重要 }, mutations: { message(state, message) { state.message = message }, updateUser(state, user) { state.name = user.name state.email = user.email }, checkLogin(state, userInfo) { //vuexのstateに格納 state.name = userInfo.name state.email = userInfo.email state.token = userInfo.token //リロードされても残すようにローカルストレージに格納 localStorage.setItem("token", state.token) localStorage.setItem("userName", state.name) localStorage.setItem("userEmail", state.email) }, } }, ); export default store;
ユーザー情報(名前・メールアドレス・トークン)をAPIを介してUserテーブルから受け取り、Vuexのstateに格納。
Vuexのstateとは、簡単に言うとどのコンポーネントファイルから見ても共通の変数を取りに行ける場所みたいなやつ。
でも更新(リロード)をすると中身が消えてしまう。
知らんけど。
そのため、Vuexのstateに保存した内容はローカルストレージ(リロードしても消えない共通の変数を持てる場所)にも保存する。
ヘッダー
お待ちかねヘッダー
//Header.vue <template> <header> <div> <b-navbar toggleable="lg" type="dark" variant="dark"> <b-navbar-brand href="#">NavBar</b-navbar-brand> <b-navbar-toggle target="nav-collapse"></b-navbar-toggle> <b-collapse id="nav-collapse" is-nav> <b-navbar-nav> <b-nav-item class="active" to="/">Home</b-nav-item> <b-nav-item to="/about">About</b-nav-item> <b-nav-item to="/stocks/create">Post</b-nav-item> <b-nav-item to="/stocks">Archive</b-nav-item> <b-nav-item to="/login">Login</b-nav-item> <b-nav-item to="/register">Register</b-nav-item> </b-navbar-nav> <b-navbar-nav class="ml-auto"> <b-nav-item-dropdown right v-if="isLoggedIn"> <template #button-content> <em>{{ userName }}</em> </template> <b-dropdown-item href="#">Profile</b-dropdown-item> <b-dropdown-item href="#">Sign Out</b-dropdown-item> </b-nav-item-dropdown> <button v-if="!(userName)">ログイン</button> </b-navbar-nav> </b-collapse> </b-navbar> </div> </header> </template> <script> export default { data() { return { userName: null, email: "", isLoggedIn: false, }; }, mounted() { this.$store.watch(//ここでvuexにおける「state.name」の値の変化を監視 (state, getters) => getters.getUserName, (newValue, oldValue) => { console.log('username changed! %s => %s', oldValue, newValue) this.isLoggedIn=true this.userName = newValue } ) this.userName = localStorage.userName //これがないとリロードしたときに名前が消える }, computed: {}, methods: {}, }; </script>
ホームコンポーネント
ログイン後に飛ぶべきページ
<template> <div> <div v-if="isLoggedIn"> <p>{{ user.name }}</p> <p>{{ user.email }}</p> </div> <div v-else> <p>{{ $store.state.message }}</p> </div> </div> </template> <script> import Header from '../layout/Header' import Footer from '../layout/Footer' export default { components: { Header, Footer, }, title: 'About', data() { return { user: { name: null, email: null, token: null, }, isLoggedIn: false, } }, mounted() { this.user.name = localStorage.userName this.user.email = localStorage.userEmail this.user.token = localStorage.token }, } </script>
結果
これでログイン直後にリロードせずとも
この状態になった。