Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化。
狀態?我把它理解為在data中的屬性需要共享給其他vue組件使用的部分,就叫做狀態。簡單的說就是data中需要共用的屬性。
使用Vue開發項目時,通常我們就會遇到如下幾種棘手的問題:
問題1:通過路由傳遞參數,我們會采用params或者query形式,但這兩種方式都會在URL上做手腳,如果傳遞的參數過多,會導致400 Bad Request(如:點擊表格某行,攜帶行數據跳轉到新頁面進行查看)。?
問題2:兄弟組件傳值?
問題3:多處地方使用同一數據,為節省重復請求(最為常見)
業務場景:
從A頁面攜帶下鉆參數(ID)到B頁面,然后B頁面獲取參數(ID)進行數據請求。由于要下鉆的ID過長,受瀏覽器的URL長度限制問題,我們不能采用常規的prams(xxx.com/B/:ID)或query(xxx.com/B?ID=…)形式傳遞。可以使用Vuex做中間過渡,跳轉前存儲ID信息,進入B頁面后從Vuex獲取ID信息。
如果用戶在B頁面刷新數據,則Vuex的ID狀態值會被清空無法獲取,這里只能借助localStorage進行持久化進行處理(當然,如果直接使用localstorage進行持久化存儲,而不借助Vuex也是可行的,但就是沒了響應式的效果了)
一、常見使用問題
1、Vuex,每次調用mutation之后向localstorage存值,防止刷新丟失
export default {state:{reportInfo:null},getters:{reportInfo(state){if(!state.reportInfo){state.reportInfo = JSON.parse(sessionStorage.getItem('reportInfo'))}return state.reportInfo}},mutations:{initReport:(state,data) => {state.reportInfo = datasessionStorage.setItem('reportInfo',JSON.stringify(data))},changeReportReview:(state,data) => {state.reportInfo.is_review = data;//改state的reportInfo里的某個值,
同時修改sessionStorage的值,以保證一樣sessionStorage.setItem('reportInfo',JSON.stringify(state.reportInfo))}}
}
?
2、調用時需要追加模塊名稱
如上的getters,首先從store中獲取,如果store中不存在則從localstorage中獲取(刷新)
3、頁面中如果有用戶登出操作,此時一般需要removeItem,如果頁面中有userInfo的判斷信息,如下一般需要先做個判斷userInfo存在才會去取userInfo.roleName的值,這樣就會防止頁面報錯,類似于后臺語言空指針的錯誤
<span class="el-dropdown-link">{{userInfo ? (userInfo.account ? userInfo.account : userInfo.phoneNum) : ""}}<i class="iconfont icon-user"></i>
</span>
<router-link :to="'/account'" v-if="userInfo && userInfo.roleName === 'sys'"><el-dropdown-item>添加賬戶</el-dropdown-item>
</router-link>
如果沒有removeItem的操作的話,則是不需要這樣的。如下面的reportInfo,因為用戶登出的時候,不會影響到它
<div class="suggess_requert" v-if="suggess.suggestion_level === 3"><div v-if="reportInfo.is_review === 1"><router-link v-if="userInfo && userInfo.roleName === 'dba'" :to="'/list/review'"><h3>提交審核意見</h3></router-link><h3 v-else>專家正在審核,請稍后</h3></div><router-link v-else-if="reportInfo.is_review === 2" :to="'/list/reviewInfo/' + reportInfo.report_id"><h3>查看云和恩墨專家團隊審核意見</h3></router-link><div v-else><h3 v-if="userInfo && userInfo.roleName === 'user'" @click="reviewRequest()">一鍵請求云和恩墨專家團隊幫您二次審核</h3></div>
</div>
?
?
4、state訪問狀態對象
const state ,這個就是我們說的訪問狀態對象,它就是我們SPA(單頁應用程序)中的共享值。
學習狀態對象賦值給內部對象,也就是把stroe.js中的值,賦值給我們模板里data中的值。有三種賦值方式:
(1)通過computed的計算屬性直接賦值
computed屬性可以在輸出前,對data中的值進行改變,我們就利用這種特性把store.js中的state值賦值給我們模板中的data值。
computed:{count(){return this.$store.state.count;}
}
這里需要注意的是return this.$store
.state.count這一句,一定要寫this,要不你會找不到$store的。這種寫法很好理解,但是寫起來是比較麻煩的,那我們來看看第二種寫法。
(2)通過mapState的對象來賦值
我們首先要用import引入mapState。
import {mapState} from 'vuex';//然后還在computed計算屬性里寫如下代碼:
computed:mapState({count:state=>state.count //理解為傳入state對象,修改為state.count屬性
})
這里我們使用ES6的箭頭函數來給count賦值。
(3)通過mapState的數組來賦值
computed:mapState(["count"])
這個算是最簡單的寫法了,在實際項目開發當中也經常這樣使用
5、模板獲取Mutations方法
實際開發中我們也不喜歡看到$store.commit( )這樣的方法出現,我們希望跟調用模板里的方法一樣調用。 例如:@click=”reduce” 就和沒引用vuex插件一樣。要達到這種寫法,只需要簡單的兩部就可以了:
//1、用import 引入我們的mapMutations:
import {mapGetters,mapMutations} from 'vuex'
//2、添加methods屬性,并加入mapMutations
...mapMutations(['changeReportReview']),
mapGetters、
mapActions
,都是一樣的用法
methods:{...mapMutations([ 'add','reduce']),...mapActions(['addAction','reduceAction'])
},
需要注意的是,(1)調用的時候都是需要加上this才能訪問到的;(2)getters/mutations/actions這些如果沒帶命名空間的話,就算是寫在module模塊里的這些方法,但是在使用的時候,是不需要加上模塊名稱的,而state是要加上模塊名稱的
二、狀態管理模式
“單向數據流”理念的極簡示意:
- state,驅動應用的數據源;
- view,以聲明方式將?state?映射到視圖;
- actions,響應在?view?上的用戶輸入導致的狀態變化。
我們在開發中會遇到多個組件共享狀態時,單向數據流的簡潔性很容易被破壞。
- 多個視圖依賴于同一狀態。
- 來自不同視圖的行為需要變更同一狀態。
對于問題一,傳參的方法對于多層嵌套的組件將會非常繁瑣,并且對于兄弟組件(非父子組件)間的狀態傳遞無能為力;
對于問題二,我們經常會采用父子組件直接引用或者通過事件來變更和同步狀態的多份拷貝。以上的這些模式非常脆弱,通常會導致無法維護的代碼。
三、Vuex簡介
Vuex 和單純的全局對象有以下兩點不同:
- Vuex 的狀態存儲是響應式的。當 Vue 組件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那么相應的組件也會相應地得到高效更新。
- 不能直接改變 store 中的狀態。改變 store 中的狀態的唯一途徑就是顯式地提交 (commit) mutation。這樣使得我們可以方便地跟蹤每一個狀態的變化,從而讓我們能夠實現一些工具幫助我們更好地了解我們的應用。
Vue.use(Vuex);
const store = new Vuex.Store({// 數據狀態state {...},// 更改狀態 store.commitmutations: {...},// 類似于mutation(不能直接變更狀態,可以異步操作) store.dispatchactions: {...},// 派生狀態(如,過濾、計數)getters: {...}
})// 將狀態從根組件“注入”到每一個子組件中,且子組件能通過 this.$store 訪問到。
const app = new Vue({el: '#app',store,data() {}
});
?
四、State
Vuex 使用單一狀態樹,這可以讓我們能夠直接地定位任一特定的狀態片段,在調試的過程中也能輕易地取得整個當前應用狀態的快照。需要注意,單狀態樹和模塊化并不沖突!
由于 store 中的狀態是響應式的,在組件中調用 store 中的狀態簡單到僅需要在計算屬性中返回即可。
computed: {count () {return store.state.count // this.$store.state.count}
}
mapState 輔助函數
當一個組件需要獲取多個狀態時候,將這些狀態都聲明為計算屬性會有些重復和冗余。為了解決這個問題,我們可以使用?mapState
?輔助函數幫助我們生成計算屬性。
import { mapState } from 'vuex'computed: mapState({// 映射 this.count 為 store.state.count'count',// 箭頭函數可使代碼更簡練count: state => state.count,// 傳字符串參數 'count' 等同于 `state => state.count`countAlias: 'count',// 為了能夠使用 `this` 獲取局部狀態,必須使用常規函數countPlusLocalState (state) {return state.count + this.localCount},// 使用對象展開運算符將此對象混入到外部對象中...mapState({// ...})
})
?
五、Getter
Getter(state, getters)
可以從 store 中的 state 中派生出一些狀態(如,對數據進行過濾操作)。對于多個組件需要用同一屬性時,意義重大!類似于計算屬性,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變才會被重新計算。
完整請參照?https://vuex.vuejs.org/zh-cn/getters.html
六、Mutation
mutation 必須是同步函數!!!
更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。
Vuex 中的 mutation 非常類似于事件:每個 mutation 都有一個字符串的?事件類型 (type)?和 一個?回調函數 (handler)。調用?store.commit(type, payload)
?方法來觸發mutations中的相關方法。
mutations: {increment (state, n) {state.count += n}
}store.commit('increment', 10)
?
注意:Mutation 需遵守 Vue 的響應規則
-
最好提前在你的 store 中初始化好所有所需屬性
-
當需要在對象上添加新屬性時,你應該
-
使用?
Vue.set(obj, 'newProp', 123)
, 或者 -
以新對象替換老對象。例如,利用 stage-3 的對象展開運算符我們可以這樣寫
-
state.obj = { ...state.obj, newProp: 123 }
完整請參照:https://vuex.vuejs.org/zh-cn/mutations.html
七、Action
Action 類似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接變更狀態。
- Action 可以包含任意異步操作。
- 通過?
store.dispatch
?方法觸發
組合 Action:store.dispatch
?可以處理被觸發的 action 的處理函數返回的 Promise,并且?store.dispatch
?仍舊返回 Promise。
actions: {actionA ({ commit }) {return new Promise((resolve, reject) => {setTimeout(() => {commit('someMutation')resolve()}, 1000)})}
}
//現在你可以:
store.dispatch('actionA').then(() => {// ...
})
?
?
在另外一個 action 中也可以:
// 假設 getData() 和 getOtherData() 返回的是 Promise
actions: {async actionA ({ commit }) {commit('gotData', await getData())},async actionB ({ dispatch, commit }) {await dispatch('actionA') // 等待 actionA 完成commit('gotOtherData', await getOtherData())}
}
?
一個?store.dispatch
?在不同模塊中可以觸發多個 action 函數。在這種情況下,只有當所有觸發函數完成后,返回的 Promise 才會執行。
完整請參照:https://vuex.vuejs.org/zh-cn/actions.html
八、Module
由于使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象。為了解決以上問題,Vuex 允許我們將 store 分割成模塊(module)。
默認情況下,模塊內部的 action、mutation 和 getter 是注冊在全局命名空間的——這樣使得多個模塊能夠對同一 mutation 或 action 作出響應。如果希望你的模塊具有更高的封裝度和復用性,你可以通過添加?namespaced: true
?的方式使其成為命名空間模塊。當模塊被注冊后,它的所有 getter、action 及 mutation 都會自動根據模塊注冊的路徑調整命名。
九、插件
Vuex 的 store 接受?plugins
?選項,這個選項暴露出每次 mutation 的鉤子。Vuex 插件就是一個函數,它接收 store 作為唯一參數。
const myPlugin = store => {// 當 store 初始化后調用store.subscribe((mutation, state) => {// 每次 mutation 之后調用// mutation 的格式為 { type, payload }})
}
?
然后像這樣使用:
const store = new Vuex.Store({// ...plugins: [myPlugin]
})
項目中我們會使用plugin來初始化一些數據
const initActionList = ['base/' + INIT_BUSINESS_SYSTEM_LIST,'threat/' + INIT_STD_COEFFICIENT_LIST
]
export default function (store) {for (let action of initActionList) {Bus.$once(action, () => {store.dispatch(action)})}
}
?
但是 ,使用plugin的Bus.$once去初始化請求,而不再每個使用模塊自身dispatch。會有解決不掉的兩個問題:
- 點擊某個按鈕觸發相關數據($once只適合初始化時請求)
- 某請求依賴store中的情況(刷新)
await dispatch('actionA') // 等待 actionA 完成
十、表單處理
當在嚴格模式中使用 Vuex 時,在屬于 Vuex 的 state 上使用?v-model
?會比較棘手
<input v-model="obj.message">
在用戶輸入時,v-model
?會試圖直接修改?obj.message
。在嚴格模式中,由于這個修改不是在 mutation 函數中執行的, 這里會拋出一個錯誤。使用傳統的value+input事件實現,但是比較啰嗦。
<input :value="message" @input="updateMessage">computed: {...mapState({message: state => state.obj.message})
},
methods: {updateMessage (e) {this.$store.commit('updateMessage', e.target.value)}
}
這里,可以使用雙向綁定的計算屬性:
computed: {message: {get () {return this.$store.state.obj.message},set (value) {this.$store.commit('updateMessage', value)}}
}
總結:使用 Vuex 并不意味著你需要將所有的狀態放入 Vuex。雖然將所有的狀態放到 Vuex 會使狀態變化更顯式和易調試,但也會使代碼變得冗長和不直觀。如果有些狀態嚴格屬于單個組件,最好還是作為組件的局部狀態。你應該根據你的應用開發需要進行權衡和確定。