Vuex初步認識
vuex是vue中單向資料流的一個狀態管理員模式,它可以集中儲存管理應用中所有組件的狀態,並且有一套相應的規則可以去預測資料的變化。類似與此的還有react中的redux,dva等狀態管理員模式。
一般我們的狀態管理組件含以下幾個部分:
- state 這是驅動頁面變化的資料來源
- view state資料展示的視圖
- action 在view層使用者操作資料變化的響應
vue中的資料流為單向數流
單向資料流在兄弟組件需要傳參或者多個組件需要使用同一個狀態並且多個組將都可以改變該狀態時不易進行維護。
因此,我們採取的是將多個共用的狀態抽離到一個全域單例中(實際上就是將組件的狀態抽離出來進行單獨管理),其實在redux和dva中,是將每個組件的狀態抽離到它自己的單例狀態中,並且這些單例狀態之間是互連的。
vuex中資料流的一個大概的流程是,我們再視圖層通過觸發一個一個的action到mutations中,mutataions中改變對應的state,然後該state的變化會去影響所對應的視圖層的html結構。
當然正如其他狀態機器模式一樣,如果你不打算開發大型單頁應用,也是沒必要去使用vuex。
核心概念
vuex應用的核心就是store,store中包含著我們應用中的大部分狀態,vuex的狀態儲存是快速響應的,當store中的state有變化時,相應的組件也會快速的更新。並且我們需要遵循vuex中的規則,無法直接去改變state,改變store中的狀態的唯一途徑就是通過commit.
通過每次的commit中所含的資訊,我們可以輕鬆明確的去看到我們每次改變state的意圖,這樣也方便我們將來對資料的追蹤。
state
由於vuex使用單一狀態樹,也就是一個應用中的state你可以全部放到這一個store中,但是如果你放置的狀態過多的時候,這也是挺雞肋的不是?我還沒有去看vuex如何將狀態和狀態變更的事件分布到各個子模組中去。
mapState是vuex提供的簡化資料訪問的輔助函數,mapState函數返回的是一個對象,通常,我們需要使用一個工具函數將多個對象合并為一個,以使我們可以將最終對象傳給computed屬性。
當然,使用vuex並不意味著我們將所有的狀態放入vuex,雖然將所有的狀態放到vuex會使狀態變化更顯示和易調試,但是如果全部放到了全域Store中我們的代碼會變得冗長和不直觀,所以這些個東西還需要邊開發邊權衡吧。
Getter
有時候我們需要從store中的state中派生出一些狀態,例如對列表進行過濾並計數:
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
vuex允許我們在store中定義“getter".就像計算屬性一樣,getter傳回值會根據他的依賴被緩衝起來,且只有當他的依賴值發生了改變才會被重新計算。
getter接受state作為其第一個參數:
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
getter會暴露store.getters對象:
getter類似於dva中的reducer。
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }getter也可以接受其他getter作為第二個參數:
getters: {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
store.getters.doneTodosCount // -> 1
我們可以很容易的在任何組件中調用它:
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}
mapGetters輔助函數
mapGetters輔助函數僅僅是將store中的getter映射到局部計算屬性。
例如:
computed:{
...mapGetters(['getter1','getter2'])
}
按照上述寫法,全域store中的getter1和getter2函數便可以在局部組件中使用了。
如果想將一個getter屬性另取一個名字,使用對象形式:
computed:{
...mapGetters({
newGetter:"oldStoreGetter"
})
}
Mutation
更改Vuex的store中的狀態的唯一辦法是提交mutation.Vuex中的mutation非常類似於事件,這有點類似於dva中的effects,每一個mutation都會有一個字串的事件類型和一個回呼函數。這個回呼函數就是我們實際進行狀態更改的地方。並且他會接受state作為第一個參數:
mutations: {
setAdminInfo(state,adminInfo){
state.adminInfo = adminInfo;
},
setCityList(state,cityList){
state.cityList = cityList
}
}
當然了,我們還不能直接調用mutation handler,我們想要執行此函數時,需要以相應的type調用store.commit方法:
當我們需要給mutation傳參時,我們需要通過payload來進行,上述例子中的adminInfo就是我們要傳的的參數,在vuex中叫payload,當然,官方的建議是payload盡量是一個對象,這樣我們在使用的時候能夠更好的去追蹤資料流。
當然,更好的調用mutation的方式是commit包含type屬性的對象:
store.commit({
type:'updateAdminInfo',
adminInfo:{
username:'allen',
password:'qwe123',
}
})
mutation需要遵守vue的響應規則
既然Vuex的store中的狀態是響應式的,那麼當我們變更狀態時,監視狀態的vue組件也會自動更新。這就意味著Vuex中的mutation也需要與Vue一樣遵守一些注意事項:
- 在store中初始化好所有的屬性
- 使用新對象替換老對象,即每次都返回一個全新的state對象,可以使用Object.Assign的方式。
使用常亮替代Mutation事件類型
使用常量替代mutation事件類型再各種flux實現中是很常見的模式。可以使我們整個項目的資料流向一目瞭然。
當然,另外重要的一點就是mutation必須是同步函數,當我們再debug一個app並且觀察devtool的mutation日誌時,每一條mutation被記錄,都需要捕捉到前一狀態和後一狀態的快照。
我們可以在組件中使用this.$store.commit("example")提交mutation,或者使用mapMutations輔助函數將組件中的methods映射為store.commit調用。
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // 將 `this.increment()` 映射為 `this.$store.commit('increment')`
// `mapMutations` 也支援載荷:
'incrementBy' // 將 `this.incrementBy(amount)` 映射為 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 將 `this.add()` 映射為 `this.$store.commit('increment')`
})
}
}
Action
Action類似於mutation,不同在於:
- Action提交的是mutation,而不是直接變更狀態。
- Action可以包含任意非同步作業。
下邊來看一個簡單的action的例子:
jsconst store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') } } }) const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') } } })
Action通過store.dispatch方法觸發:
store.dispatch('increment');
使用action進行觸發mutation可以不用受必須同步執行的約束。我們可以在Action內部執行非同步作業。
同樣Action中也可以使用payload來傳遞參數
store.dispatch({
type:"increment",
amount:10
})
同時,Action通常是非同步,store.dispatch可以處理被觸發的action的處理函數返回的Promise,並且store.dispatch仍然返回Promise.
現在,我們已經不需要去執行promise.then函數了,我們直接用async/await就可以了。
async action1({commit}){
commit('gotData', await getData());
}
Module
使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象中。當應用變得非常複雜時,store對象就會變得十分臃腫。
vuex允許我們將store分割成模組,每個模組擁有自己的state,mutation,acion,getter,甚至是嵌套子模組,從上至下進行分割。
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的狀態
store.state.b // -> moduleB 的狀態
模組的局部狀態
對於模組內部的mutation和getter,接受的第一個參數是模組的局部狀態物件。同樣的,對於模組內部的action,局部狀態通過context.state暴露出來,根節點狀態則為context.rootState;
對於模組內部的getter,根節點狀態會作為第三個參數暴露出來;
命名空間
預設情況下,模組內部的action,mutation,和getter是註冊再全域命名空間的--這樣可以使多個模組能夠對同一個mutation或action作出響應。
當然,模組化中,我們可以使用namespaced:true的方式使其成為命名空間模組。當模組被註冊之後,他的所有getter、Action、以及mutation都會自動根據模組註冊的路徑調整命名。啟用了命名空間之後,便是將一個整體的store給分割成了一個個模組。
模組動態註冊
在store建立之後,你可以使用store.registerModule方法註冊模組:
// 註冊模組 `myModule`
store.registerModule('myModule', {
// ...
})
// 註冊嵌套模組 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
// ...
})
當然,我們也可以通過store.unregisterModule(moduleName)來動態卸載模組。但是我們無法使用此方法去卸載靜態模組。
Vuex並不限制你的代碼結構。但是,他規定了一些需要遵守的規則:
- 應用程式層級的狀態應該集中到單個store對象中。
- 提交mutation是更改狀態的唯一辦法,並且這個過程是同步的。
- 非同步邏輯都應該封裝在Action裡面。
本文永久更新連結地址:https://www.bkjia.com/Linux/2018-03/151498.htm