5.modules
背景:
在Vuex中所有的狀态都放在state裡面,如果項目比較複雜,那state是一個很大的對象,store對象也将對變得非常大,難于管理。
modules:可以讓每一個子產品擁有自己的state、mutations、action、getters,使得結構非常清晰,友善管理,甚至是嵌套子子產品——從上至下進行同樣方式的分割。
尤其在多人開發同一個項目的時候,自己負責自己相關的子產品的state,會讓操作state都會變得更加直覺。
先來看下示例:
const moduleA = {
state: {
name:'lily',
role:'超級管理者',
sex:'女'
},
getters: {
myProfile(state, getters, rootState, rootGetters) {
// 這裡的 `state` 對象是子產品的局部狀态
console.log(rootState,getters,rootState,rootGetters,'lily')
return state.name + state.role
}
},
mutations: {
SET_ROLE(state,str) {
// 這裡的 `state` 對象是子產品的局部狀态
state.role = str;
}
},
actions: {
SET_ROLE (context) {
//在 action 中,由于它接收過來的資料都被包在context對象中的,是以解出來沒有什麼順序的限制,你可以這樣:
let { state,commit,rootState, } = context;
//也可以這樣:
//let { commit,state,rootGetters,rootState} = context;
commit('SET_ROLE','普通管理者');
console.log(context)
}
},
}
const moduleB = {
state: {
token:'kfep0f91265jiefj2512',
permission:[]
},
getters: {
getToken({state, getters, rootState}) {
//console.log(state, getters, rootState)
return state.token
}
},
mutations: { },
actions: { }
}
const store = new Vuex.Store({
state: {
todoLists: [1,5,5,2,3,6,6,5,0,8],
},
mutations:{
// 新增list
ADDLIST(state, item) {
state.todoLists.push(item);
console.log(state.todoLists,'state.todoLists')
},
},
actions:{
addList(context, item) {
context.commit('ADDLIST', item);
},
},
modules: {
a: moduleA,
b: moduleB
}
})
new Vue({
el: '#app',
data: {
name: 'init name'
},
store: store,
mounted: function() {
//console.log(this,myStore.getters.todoCount);
},
computed:{
ROLE:function() {
this.$store.dispatch('SET_ROLE');
},
...Vuex.mapGetters(['myProfile'])
}
})
store.state.a // -> moduleA 的state狀态
store.state.b // -> moduleB 的state狀态
1. 子產品的局部狀态
我們從上面這個示例中作如下分析:
1.moduleA中的mutation和getter接受到的第一個參數state是moduleA的局部狀态對象,隻屬于子產品本身所有,并且對于moduleA中的getter,根節點狀态會作為第三個參數暴露出來,這個參數可以讓我們通路 store 根節點的資料 state
const moduleA = {
state: {
name:'lily',
role:'超級管理者',
sex:'女'
},
getters: {
myProfile(state, getters, rootState) {
// 這裡的 `state` 對象是子產品的局部狀态
console.log(rootState,getters,rootState,'lily')
return state.name + state.role
}
},
mutations: {
SET_ROLE(state,str) {
// 這裡的 `state` 對象是子產品的局部狀态
state.role = str;
}
}
}
- moduleA中的action局部狀态通過context.state暴露出來,根節點狀态則為context.rootState
const moduleA = {
state: {
name:'lily',
role:'超級管理者',
sex:'女'
},
actions: {
SET_ROLE (context) {
let { state,commit,rootState, } = context;
commit('SET_ROLE','普通管理者');
console.log(context)
}
},
}
2.命名空間
前面我們已經知道了,子產品内部的action、mutation和getter預設是注冊在全局命名空間的,它有以下兩個弊端:
- 不同子產品中有相同命名的mutations、actions時,不同子產品對同一 mutation 或 action 會作出響應。
- 當一個項目中store分了很多子產品的時候,在使用輔助函數mapState、mapGetters、mapMutations、mapActions時,很難查詢,引用的state、getters、mutations、actions來自于哪個子產品,不便于後期維護。
如果我們隻想讓他們在目前的子產品中生效,應該怎麼辦呢?通過添加namespaced:true 的方式使其成為帶命名空間的子產品。當子產品被注冊後,它的所有 getter、action 及 mutation 都會自動根據子產品注冊的路徑調整命名。
我們把上面的代碼添加namespaced:true來看下
const moduleA = {
namespaced:true,
state: {
name:'lily',
role:'超級管理者',
sex:'女'
},
actions: {
SET_ROLE (context) {
let { state,commit,rootState, } = context;
commit('SET_ROLE','普通管理者');
console.log(context)
}
},
}
以上代碼會報下面這樣的錯誤:
vuex.js:423 [vuex] unknown action type: SET_ROLE
vuex.js:855 [vuex] unknown getter: myProfile
這是因為在全局actions已經找不到這個SET_ROLE這個事件類型了,因為他的路徑變了,不再屬于全局了,他僅僅屬于moduleA了。是以我們需要修改成這樣:
computed:{
ROLE:function() {
this.$store.dispatch('a/SET_ROLE');
},
//這裡的a是moduleA的子產品命名空間名字
...Vuex.mapGetters('a',[myProfile'])
}
這裡需要注意一點的就是,如果一個子產品啟用了命名空間,那麼啟用了命名空間的 getter 和 action 會收到局部化的getter,dispatch和commit。換言之,你在使用子產品内容(moduleassets)時不需要在同一子產品内額外添加空間名字首。更改 namespaced 屬性後不需要修改子產品内的代碼。
2.1 那麼我們如何在帶命名空間的子產品内通路全局内容呢?
通過前面的學習,我們要重點劃線标記下:
++如果你希望使用全局 state 和 getter,rootState 和 rootGetters 會作為第三和第四參數傳入 getter,也會通過 context 對象的屬性傳入 action。++
如果我想要在帶命名空間的子產品内dispatch actions 或者 commit mutation,那麼将 { root: true } 作為第三參數傳給 dispatch 或 commit 即可。來看下面的示例:
const moduleA = {
namespaced:true,
state: {
name:'lily',
role:'超級管理者',
sex:'女'
},
actions: {
SET_ROLE (context) {
let { state,commit,rootState, } = context;
commit('SET_ROLE','普通管理者');
//commit('ADDLIST','huanle',{root:true})
dispatch('addList','lily',{root:true})
console.log(context)
}
},
}
通過上面的代碼,todoLists裡面多了個lily這項,說明我們從子產品内部分發全局的actions已經成功。
2.2如何在帶命名空間的子產品内注冊全局 action
若需要在帶命名空間的子產品注冊全局 action,你可添加 root: true,并将這個 action 的定義放在函數 handler 中。
const moduleA = {
namespaced:true,
state: {
name:'lily',
role:'超級管理者',
sex:'女'
},
actions: {
//SET_ROLE (context) {
//let { state,commit,rootState, } = context;
//commit('SET_ROLE','普通管理者');
//commit('ADDLIST','huanle',{root:true})
//dispatch('addList','lily',{root:true})
//console.log(context)
//},
//若需要在帶命名空間的子產品注冊全局 action,你可添加 root: true,并将這個 action 的定義放在函數 handler 中
SET_ROLE: {
root:true,
handler (namespacedContext, payload) {
let { state,commit,rootState,dispatch } = namespacedContext;
commit('SET_ROLE','普通管理者');
commit('ADDLIST','huanle',{root:true})
//dispatch('addList','lily',{root:true})
console.log(state,rootState,'在帶命名空間的子產品注冊全局 action已經成功')
}
}
},
}
簡單解釋下,這裡的 namespacedContext 就相當于目前子產品的上下文對象,payload 是調用的時候所傳入的參數,當然也叫載荷。
示例就講到這裡吧,接下來繼續學習下一要點。
3.帶命名空間的綁定函數
當使用 mapState, mapGetters, mapActions 和 mapMutations 這些函數來綁定帶命名空間的子產品時,寫起來可能比較繁瑣:先看下這裡的示例,這是第一種方式:
computed:{
ROLE:function() {
this.$store.dispatch('a/SET_ROLE');
},
//這裡的a是moduleA的子產品命名空間名字
...Vuex.mapGetters({
myProfile:'a/myProfile'
})
}
對于這種情況,你可以将子產品的空間名稱字元串作為第一個參數傳遞給上述函數,這樣所有綁定都會自動将該子產品作為上下文。于是上面的例子可以簡化為:
computed:{
ROLE:function() {
this.$store.dispatch('a/SET_ROLE');
},
//這裡的a是moduleA的子產品命名空間名字
...Vuex.mapGetters('a',[myProfile'])
}
第二種方式:
你可以通過使用createNamespacedHelpers建立基于某個命名空間輔助函數。它傳回一個對象,對象裡有新的綁定在給定命名空間值上的元件綁定輔助函數:
這裡我就直接引用官方的例子了:
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
computed: {
// 在 `some/nested/module` 中查找
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// 在 `some/nested/module` 中查找
...mapActions([
'foo',
'bar'
])
}
}
4.子產品動态注冊
哈哈。這部分内容各位看官就去官方文檔翻翻看吧,因為部落客覺得動态子產品一般在業務項目中用得特别少,幾乎用不到,是以隻學項目中需要用到的,但有可能會在第三方庫中,類似封裝好的一些架構中用到這些,是以大家有需求需要動态添加子產品時,去看下官方文檔就好,是以這裡也不贅述了。謝謝大家。
寫在最後
至此,Vuex 的核心概念就已經全部講完了。雖然這套架構有點難,但其實隻要我們花點心思在上面,也是能弄懂的。大家一定要在項目中多運用,多實操。