To-Do Calendar - Day9 Vue Router & Vuex 問題解決紀錄

寫一下在實作前端登入驗證的過程中所遇到的問題及解決方法。

問題1:重新整理頁面後,state 資料狀態丟失

store 中的資料是儲存在執行記憶體中的,所以當頁面重新整理時,store 中的資料就會重新變為初始狀態。導致雖然已經登入了,但一重新整理頁面,Vuex 狀態被清除而網頁又回到登出狀態。

image

一刷新頁面,又出現請重新登入


解決方法

在登入成功後,將使用者登入資訊也保存一份在 sessionStorage,當頁面重整時再透過 sessionStorage 重新賦值給 Vuex

  • src/store/index.js
    • 後來決定拿掉 isLogin,改是否拿到 token 來判斷登入狀態
    • sessionStorage 資料的寫入和移除統一寫在 Vuex
    state: {
      isLoginModalOpen: false,
      userId: sessionStorage.getItem("userId") || "", //设置初始化的值(若在 sessionStorage 中不存在則為空字串)
      userName: sessionStorage.getItem("userName") || "",
      token: sessionStorage.getItem("token") || "",
    },
    mutations: {
      isLoginModalOpen(state, status) {
        state.isLoginModalOpen = status;
      },
      setUserId(state, userId) {
        state.userId = userId;
      },
      setUserName(state, userName) {
        state.userName = userName;
      },
      setToken(state, token) {
        state.token = token;
      },
      setStoreToSession(state) {
        sessionStorage.setItem("userId", state.userId);
        sessionStorage.setItem("userName", state.userName);
        sessionStorage.setItem("token", state.token);
      },
      removeSession(state) {
        sessionStorage.getItem("userId", state.userId);
        sessionStorage.getItem("userName", state.userName);
        sessionStorage.getItem("token", state.token);
      },
    },
    
  • src/pages/index.vue
      sendUserLogin() {
        let self = this;
        let result = true;
        if (self.checkLoginInput()) {
          //send login API to server
          if (result) {
            self.$store.commit("isLoginModalOpen", false);
            self.$store.commit("setUserId", "user01");
            self.$store.commit("setUserName", "test");
            self.$store.commit("setToken", "123");
            self.$store.commit("setStoreToSession"); //提交將使用者登入資訊寫入 sessionStorage
            setTimeout(() => {
              self.$router.push("innerPage/todoLists");
            }, 1000);
          } else {
            self.serverErrorMsg = "帳號或密碼錯誤";
          }
        }
      },
    
  • src/router/index.js
    router.beforeEach((to, from, next) => {
      if (to.meta.requireAuth) {
        if (sessionStorage.getItem("token")) { //登入狀態改從 sessionStorage 讀取 token 判斷
          next();
        } else {
          alert("請重新登入");
          store.commit("isLoginModalOpen", true);
          next("/");
        }
      } else {
        next();
      }
    });
    

運行結果

image



問題2:在瀏覽器輸入內頁網址,導回首頁後沒有開啟登入 Modal

在未登入狀態,從首頁輸入內頁網址,能正常運行攔導回首頁並自動開啟登入 Modal,但在瀏覽器輸入內頁網址,被導回首頁後卻沒有自動開啟登入 Modal,點 Start 按鈕也無法開啟登入 Modal。

image

從首頁輸入內頁網址(自動開啟登入 Modal:成功)

image

在瀏覽器輸入內頁網址(自動開啟登入 Modal:失敗)


我在 index.vue 的 created hook、mounted hook 和 router 都加了 log,來比較一下兩邊的 console 內容:

從首頁輸入內頁網址

image

一進首頁先執行 created function 和 mounted function,直到輸入內頁網址,被導航守衛攔截後提交 isLoginModalOpen 為 true,觸發 watch 開啟登入 Modal。

在瀏覽器輸入內頁網址

image

在進到內頁網址之前,先被導航守衛攔截後提交 isLoginModalOpen 為 true,導回首頁才執行 created function 和 mounted function。
而 computed 屬性是在 created 階段建立,DOM 節點、事件都綁定至 Vue 的實體是在 mounted 階段。因為此次操作的 Vue 實體是在導航守衛導回首頁之後才建立,所以無法監聽到在建立之前的 isLoginModalOpen 狀態改變,來打開登入 Modal(就算監聽到應該也打不開,因為 Vue 實體與掛載還沒完成)。

而按 Start 按鈕也無法開啟登入 Modal,是因為 isLoginModalOpen 在被導航守衛提交為 true 後,再點 Start 按鈕經過導航守衛攔截後還是提交 isLoginModalOpen 為 true,watch 沒有監聽到 isLoginModalOpen 狀態改變,所以並不會開啟登入 Modal。

image

Vue 的生命週期與 Hooks function



解決方法

在 mounted function 補上一個判斷,如果 isLoginModalOpen 為 true,就開啟登入 Modal,代表這是直接在瀏覽器輸入內頁網址過來的,因為可以在 Vue 實體建立前就提交 isLoginModalOpen 為 true 的就只有 router 了,補上之後後續登入 Modal 又能正常運作。

Warning
如果要對 DOM 做動作,記得要在 mounted 掛鉤中,它如同 onload 事件,或是 jQuery 中的 ready()。

image


延伸閱讀

參考資料



comments

comments powered by Disqus