To-Do Calendar - Day8 Vuex 狀態管理(下)
接下來要實作對 url 進行攔截,當用戶未登入時,導回首頁並自動開啟登入 Modal 的部分。
Vuex 的運行原理(單向數據流)
- 在組件中用 dispatch 方法去觸發 action 事件
- 在 Actions 中執行同步/非同步的操作,並 commit 至 Mutations
- 在 Mutations 中更新 State 狀態
- State 再回應到組件中進行 Render
Vuex 的核心概念
- State:儲存資料狀態的地方,如同全域的 data
- Actions:可以執行同步和非同步的操作(如 ajax、setTimeout..等),但不會直接更新資料狀態
- Getter:加工資料呈現,如同全域的 computed
- Mutations:實際更新資料狀態,只能執行同步的操作
- Modules:將 store 分割成模組
Warning重點整理:
更新 Vuex 的 store 中的資料狀態的唯一方法是提交 Mutations。
Mutations 只能做同步的操作,如果有非同步的需求,則要使用到 Actions。
Actions 和組件都不能直接修改 State,只有 Mutations 可以更改 State。
取得 store 中 state 資料的方法
- 方法1:直接使用 $store 來取得 state 中的資料
this.$store.state.isLogin;
- 方法2:定義在 computed 中,並在 template 載入
computed: { isLogin () { return this.$store.state.isLogin; } }
{{ isLogin }}
- 方法3:在組件載入 mapState,並在 computed 內使用 mapState 輔助函數
import { mapState } from "vuex";
或是computed: mapState(["isLogin","isLoginModalOpen","userId","userName"])
mapState 其實就是 Vuex 的一個語法糖,相當於下面的程式碼,mapState 適合用在從 store 取多個 state 資料的情境,能使程式碼更簡潔(mapGetters、mapMutations、mapActions 同理,引入後就能直接調用)computed: { ...mapState(["isLogin","isLoginModalOpen","userId","userName"]) },
computed:{ isLogin() { return this.$store.state.isLogin; }, isLoginModalOpen() { return this.$store.state.isLoginModalOpen; }, userId() { return this.$store.state.userId; }, userName(){ return this.$store.state.userName; }, ... }
Infoes6 中的展開運算符…,是將 mapState 映射的計算屬性追加上去,而不是覆蓋原有的,所以可以和組件自定義的計算屬性一起使用。
更新 store 中 state 資料的方法
-
方法1:在組件中直接修改 state (這是錯誤的做法!請避免這樣做)
this.$store.state.isLogin = true;
Warning從 Vue DevTools 工具的 Vuex 裡觀察,方法1並沒有更新 store 中 state 的資料。 -
方法2:在組件中使用 store.commit(),提交 Mutations
mutations: { isLoginModalOpen (state, status) { state.isLoginModalOpen = status; }
openLoginModal() { this.$store.commit("isLoginModalOpen", true); }
-
方法3:通過 $store.dispatch() 觸發 Actions,提交 Mutations
actions: { updateLoginModalStatus(context, status) { context.commit("isLoginModalOpen", status); } }, mutations: { isLoginModalOpen (state, status) { state.isLoginModalOpen = status; }
openLoginModal() { this.$store.dispatch("updateLoginModalStatus", true); }
Info重點整理:
直接獲取 state 的資料,用 this.$store.state.值
透過加工 state 得到新的資料,用 this.$store.getters.值
非同步修改 state 的資料狀態,用 this.$store.dispatch(“action 方法”)
同步修改 state 的資料狀態,用 this.$store.commit(“mutation 方法”)
實作前端登入驗證
因為很多地方都需要用到登入狀態和登入 Modal 狀態,所以將登入狀態(isLogin)和登入 Modal 狀態(isloginModalOpen)放進 Vuex 管理。
想法是將狀態和操作分開,把登入 Modal 的開關操作統一交給 watch 去控制,一旦 watch 偵測到 isloginModalOpen 狀態變更,就開啟登入 Modal 或關閉登入 Modal,其他組件只負責根據事件結果提交 isloginModalOpen 狀態。
直接來看程式碼吧!
src/store/index.js
- 定義變數和配置 mutations 方法
export default new Vuex.Store({ state: { isLogin: false, isLoginModalOpen: false, ... }, mutations: { isLogin(state, status) { state.isLogin = status; }, isLoginModalOpen (state, status) { state.isLoginModalOpen = status; }, ... });
src/pages/index.vue
- 在 computed 讀取 isLoginModalOpen 的狀態,並在 watch 監聽與控制登入 Modal 操作
computed: { ...mapState(["isLoginModalOpen"]), }, watch: { isLoginModalOpen() { if (this.$store.state.isLoginModalOpen) { $("#loginModal").modal({ backdrop: "static", keyboard: false }); console.log("是watch開的"); } else { $("#loginModal").modal("hide"); console.log("是watch關的"); } } }
觸發更新登入 modal 狀態的事件
-
點首頁的 Start 按鈕,直接轉交給 vue-router 的導航守衛判斷要攔截或放行
openLoginModal() { let self = this; self.$router.push("innerPage/todoLists"); },
-
取消或按x關閉登入 Modal(modal 設定禁用空白處點擊關閉,避免有操作繞過觸發關閉事件)
closeLoginModal() { let self = this; self.resetForm(); self.$store.commit("isLoginModalOpen", false); }
-
點切換到註冊連結
openRegisterModal() { let self = this; self.resetForm(); self.$store.commit("isLoginModalOpen", false); $("#registerModal").modal({ backdrop: "static", keyboard: false }); },
-
點登入按鈕,經過前端檢查輸入框後送登入 API,經後端驗證後返回登入結果,登入成功時 isLogin 才會更新為 true(API 待實作,這邊先模擬)
sendUserLogin() { let self = this; let result = true; if (self.checkLoginInput()) { //send login API to server if (result) { self.$store.commit("isLogin", true); self.$store.commit("isLoginModalOpen", false); self.$store.commit("setUserId", "userId"); self.$store.commit("setUserName", "userName"); self.$store.commit("setToken", "token"); setTimeout(() => { self.$router.push("innerPage/todoLists"); }, 1000); } else { self.serverErrorMsg = "帳號或密碼錯誤"; } } }
Update只有關閉登入 Modal 和登入成功後會提交 isLoginModalOpen 為 false。 -
跳轉頁面時,若該頁面需要登入才能訪問,會檢查 isLogin 的值
router.beforeEach((to, from, next) => { if (to.meta.requireAuth) { if (store.state.isLogin) { next(); } else { alert("請重新登入"); store.commit("isLoginModalOpen", true); next({ path: "/", } } } else { next(); } });
Update只有被導航守衛攔截後會提交 isLoginModalOpen 為 true。
運行結果
從首頁點 Start 按鈕 從首頁輸入內頁網址
延伸閱讀
- Vuex 官網 State、Mutations、Actions
- Vuex系列(三) – store.commit和store.dispatch的區別及用法
- Vuex詳解一:徹底弄懂state、mapState、mapGetters、mapMutations、mapActions