Vuex模組化應用實踐示例
Vuex作為Vue全家桶的成員之一,重要性肯定不用多說,正在做Vue專案的同學,隨著專案需求、功能逐漸增加,用到Vuex也是早晚的事兒,作為一個前端,只能面對現實:學不動也得學!
這篇文章主要介紹Vuex在大型專案中的模組化及持久化應用實踐,下面正文開始
Vuex的應用場景
- 多個元件檢視共享同一狀態時(如登入狀態等)
- 多個元件需要改變同一個狀態時
- 多個元件需要互相傳遞引數且關係較為複雜,正常傳參方式變得難以維護時
- 持久化儲存某些資料
所以我們把元件共享的狀態抽離出來,不管元件間的關係如何,都通過Vuex來處理
組織store目錄
我們先按模組化的方式組織store目錄,並在Vue根例項中註冊store,Vuex 通過 store 選項,提供了一種機制將狀態從根元件“注入”到每一個子元件中
src ├── ... ├── main.js ├── App.vue └── store ├── index.js # 我們組裝模組並匯出 store 的地方 └── modules ├── product.js # 產品模組 ├── windowInfo.js # 視窗資訊模組 └── user.js # 登入模組
src/store/index.js
import Vue from 'vue' import Vuex from 'vuex' import user from './modules/user' import product from './modules/product' import windowInfo from './modules/windowInfo' Vue.use(Vuex) export default new Vuex.Store({ modules: { // 註冊modules中的模組 user,product, windowInfo } })
src/main.js
import ... import store from './store' // 新增這行 new Vue({ el: '#app',router,store,// 注入到根例項 template: '<App/>',components: { App } })
store的屬性
state(狀態物件)
state中存放多頁面共享的狀態欄位
getters
相當於當前模組state的計算屬性
mutations
如果想更新state中的欄位,提交mutations中定義的事件是唯一的方式(key為事件名,value是一個函式),但是這個事件函式必須是同步執行的
actions
可以定義非同步函式,並在回撥中提交mutation,就相當於非同步更新了state中的欄位
vuex資料傳遞規則
使用方法
把視窗的高度和寬度存到Vuex中,並且每當視窗被resize,state中的高度和寬度自動更新
src/store/modules/windowInfo.js
import { MU_WIN_RESIZE } from '../../common/constants' const windowInfo = { state: { // 初始化 winHeight: 0,winWidth: 0 },mutations: { // 這裡把事件名統一抽離到constants.js統一管理,方便維護,避免重複。 // 當然,你也可以不這麼寫。。 // mutation事件接受的第一個引數是當前模組的state物件 // 第二個引數是提交事件時傳遞的附加引數 [MU_WIN_RESIZE] (state,payload) { const { winWidth,winHeight } = payload state.winWidth = winWidth state.winHeight = winHeight } },actions: {},getters: {} } export default windowInfo
src/common/constants.js
export const MU_WIN_RESIZE = 'MU_WIN_RESIZE' // 更新視窗尺寸
下面開啟專案的根元件新增監聽resize事件和提交mutation事件邏輯
src/App.vue
<!--上面的template我就不往這兒放了--> <script> import { _on,_off,getClientWidth,getClientHeight } from './common/dom' import { MU_WIN_RESIZE } from './common/constants' import { mapMutations } from 'vuex' export default { name: 'app',data () { return {} },mounted () { this.handleResize() // 這裡對addEventListener方法做了IE相容處理,就不貼出來了,反正事件監聽你們都會 _on(window,'resize',this.handleResize) },beforeDestroy () { _off(window,methods: { // 物件展開運算子,不熟悉的同學該學學ES6了 ...mapMutations({ // 對映 this.winResize 為 this.$store.commit(MU_WIN_RESIZE) winResize: MU_WIN_RESIZE }),handleResize () { const winWidth = getClientWidth() const winHeight = getClientHeight() this.winResize({ winWidth,winHeight }) } } } </script>
到這一步,在拖動視窗觸發‘resize'事件的時候,就會觸發‘MU_WIN_RESIZE'這個mutation事件並把視窗寬高寫入vuex,下面我們隨便找個頁面看能不能獲取到我們寫入的值
<template> <div class="row">視窗高:{{winHeight}} 視窗寬:{{winWidth}}</div> </template> <script> import { mapState } from 'vuex' export default { name: 'test',computed: { // 把state寫入計算屬性 // 如果使用mapGetters也是寫入計算屬性 ...mapState({ winHeight: state => state.windowInfo.winHeight,winWidth: state => state.windowInfo.winWidth }) },} </script>
有的時候我們會從後端獲取一些下拉框選項的靜態常量,而且很多頁面都能用到,這個時候用Vuex是比較好的選擇,涉及到後端獲取,就要用到可以使用非同步的actions了
src/store/modules/product.js
import {MU_PRODUCT_UPDATE_CONSTANTS} from '../../common/constants' const product = { state: { productConstants: [] },mutations: { [MU_PRODUCT_UPDATE_CONSTANTS] (state,payload) { state.productConstants = payload } },actions: { // action函式第一個引數接受一個與 store 例項具有相同方法和屬性的 context 物件, // 因此你可以呼叫 context.commit 提交一個 mutation, // 或者通過 context.state 和 context.getters 來獲取 state 和 getters // 這裡雖然能獲取到state,但是不建議直接修改state中的欄位 async getProductConstants ({ commit },payload) { try { // 請求介面,如果需要引數可以通過payload傳遞 const res = await this.$api.product.getConstants() commit(MU_PRODUCT_UPDATE_CONSTANTS,res) } catch (e) { console.error(e) } } },getters: {} } export default product
下面觸發這個getProductConstants事件,觸發這個action事件的位置需要注意一下,假設你有5個元件需要使用這個state,那就應該在這5個元件共同的父元件中呼叫一次action(找不到就在根例項中呼叫),然後在各個子元件中通過mapState或mapGetters獲取state,千萬不要每個元件使用前都呼叫一次action方法!
src/App.vue
<!--為了更直觀的展示action,把之前的程式碼刪掉了--> <script> import { mapActions } from 'vuex' // 注意是mapActions export default { name: 'app',created () { // 觸發請求 this.getProductConstants() } methods: { ...mapActions([ // 對映 this.getProductConstants 為 this.$store.dispatch('getProductConstants') 'getProductConstants' ]) } } </script>
mapGetters,mapMutations,mapActions,這幾個函式可以接受物件也可以接受陣列作為引數,如果你需要在元件中以別的名字呼叫該事件(像上面的mapMutations)就可以傳入物件,key為新命名,value是store中定義的名字;否則的話傳陣列就好了。
那麼問題來了,既然是非同步操作,我想在操作結束後乾點兒別的怎麼做呢?
很簡單,呼叫action中的非同步函式(this.$store.dispatch)返回的是一個Promise,如果你跟我一樣用的是async await:
<!--為了更直觀的展示action,把之前的程式碼刪掉了--> <script> import { mapActions } from 'vuex' // 注意是mapActions export default { name: 'app',async created () { // 觸發請求 await this.getProductConstants() // 接下來執行的操作會等待上面函式完成才會執行 } methods: { ...mapActions([ // 對映 this.getProductConstants 為 this.$store.dispatch('getProductConstants') 'getProductConstants' ]) } } </script>
如果你用的不是async await那就麻煩一點,在actions中定義事件的時候return一個new Promise,官方文件中有一個例子
表單處理
當你把從state中獲取的欄位填在v-model中時,如果使用者修改表單資料,v-model會嘗試直接修改store中的資料,這樣做會有兩個問題:
- 破壞了vuex的資料傳遞規則,如果想修改state中的資料只能通過提交一個mutation
- 控制檯報錯:計算屬性沒有setter
官方提供了兩種解決方法,我更傾向於下面這種,給計算屬性新增setter,並在setter中提交mutation修改state:
<template> <input v-model="message"> </template> <script> export default { name: 'app',computed: { message: { get () { return this.$store.state.test.message },set (value) { this.$store.commit('updateMessage',value) } } } methods: {} } </script>
Vuex持久化
推薦外掛vuex-persist
安裝外掛:
npm install --save vuex-persist
引入、配置、載入外掛:
src/store/persist.js
import VuexPersistence from 'vuex-persist' const persist = new VuexPersistence({ // 其他引數看文件 storage: window.sessionStorage }) export default persist.plugin
src/store/index.js
import ... import persist from './persist' Vue.use(Vuex) export default new Vuex.Store({ modules: { user,product,windowInfo },plugins: [persist] })
現在重新整理瀏覽器資料也不會重置了!
總結
以上就是vuex比較常規的操作了,第一次看官方文件的我是懵逼的、無助的,但是用了一段時間vuex再重新看文件的時候會有很多收穫。希望對大家的學習有所幫助,也希望大家多多支援我們。