vuex中幾個核心概念: state, getters, mutations, actions, module
getters
可以認為是store的計算屬性;與計算屬性一樣,getter的返回值會根據它的依賴緩存起來,且只有當它的依賴值發生變化才會被重新計算
mapGetters
輔助函數僅僅是將 store 中的 getter 映射到局部計算屬性:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用對象展開運算符將 getter 混入 computed 對象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
mutations 只能是同步操作
更改vuex的store中的狀態的唯一方法就是提交 mutations
在 mutation 中混合異步調用會導致你的程序很難調試。例如,當你能調用了兩個包含異步回調的 mutation 來改變狀態,你怎么知道什么時候回調和哪個先回調呢?
mutation必須是同步函數
mutations: {
someMutation (state) {
api.callAsyncMethod(() => {
state.count++
})
}
}
現在想象,我們正在 debug 一個 app 并且觀察 devtool 中的 mutation 日志。每一條 mutation 被記錄,devtools 都需要捕捉到前一狀態和后一狀態的快照。然而,在上面的例子中 mutation 中的異步函數中的回調讓這不可能完成:因為當 mutation 觸發的時候,回調函數還沒有被調用,devtools 不知道什么時候回調函數實際上被調用——實質上任何在回調函數中進行的狀態的改變都是不可追蹤的。
在組件中提交mutations
你可以在組件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations
輔助函數將組件中的 methods 映射為 store.commit 調用(需要在根節點注入 store)。
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')`
})
}
}
actions 可以是異步操作
- action提交的是mutation,而不是直接更改狀態
- action 可以包含任何異步操作
分發 action
在組件中分發Action
你在組件中使用 this.$store.dispatch('xxx') 分發 action,或者使用 mapActions
輔助函數將組件的 methods 映射為 store.dispatch 調用(需要先在根節點注入 store):
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 將 `this.increment()` 映射為 `this.$store.dispatch('increment')`
// `mapActions` 也支持載荷:
'incrementBy' // 將 `this.incrementBy(amount)` 映射為 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 將 `this.add()` 映射為 `this.$store.dispatch('increment')`
})
}
}
組合 Action
Action 通常是異步的,那么如何知道 action 什么時候結束呢?更重要的是,我們如何才能組合多個 action,以處理更加復雜的異步流程?
首先,你需要明白 store.dispatch 可以處理被觸發的 action 的處理函數返回的 Promise,并且 store.dispatch 仍舊返回 Promise:
// 假設 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
module
由于使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象;當應用變得非常復雜時,store 對象就有可能變得相當臃腫。
為了解決以上問題,Vuex 允許我們將 store 分割成模塊(module)。每個模塊擁有自己的 state、mutation、action、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 的狀態
如果你希望使用全局 state
和 getter
,rootState
和 rootGetter
會作為第三和第四參數傳入 getter,也會通過 context 對象的屬性傳入 action。
命名空間
默認情況下,模塊內部的 action、mutation 和 getter 是注冊在全局命名空間的——這樣使得多個模塊能夠對同一 mutation 或 action 作出響應。
如果希望你的模塊具有更高的封裝度和復用性,你可以通過添加 namespaced: true 的方式使其成為命名空間模塊。當模塊被注冊后,它的所有 getter、action 及 mutation 都會自動根據模塊注冊的路徑調整命名。例如:
const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
// 模塊內容(module assets)
state: { ... }, // 模塊內的狀態已經是嵌套的了,使用 `namespaced` 屬性不會對其產生影響
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},
// 嵌套模塊
modules: {
// 繼承父模塊的命名空間
myPage: {
state: { ... },
getters: {
profile () { ... } // -> getters['account/profile']
}
},
// 進一步嵌套命名空間
posts: {
namespaced: true,
state: { ... },
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})
如果你希望使用全局 state 和 getter,rootState 和 rootGetter 會作為第三和第四參數傳入 getter,也會通過 context 對象的屬性傳入 action。
若需要在全局命名空間內分發 action 或提交 mutation,將 { root: true } 作為第三參數傳給 dispatch 或 commit 即可。
需要注意的點
1.默認情況下,模塊內的getter, mutation,action是注冊在全局空間的,state只注冊在局部命名空間的;
要想使模塊內的getter, mutation,action注冊在模塊命名空間,必須在模塊內加上 namespaced: true
使用命名空間在調用action時必須使用
this.$store.dispatch('hero1/getHeroInfo');
computed: {
doneTodosCount () {
return this.$store.getters['hero1/doneTodos'][0].item;
}
},
2.頁面刷新時,store中的數據會清空
解決方案
https://stackoverflow.com/questions/43027499/vuex-state-on-page-refresh
3.雙向綁定(v-model)和 vuex 是否沖突
<template>
<div >
<input type="text" v-model="obj.message">
</div>
</template>
<script>
export default {
computed: {
obj() {
return this.$store.state.test.obj;
},
},
}
</script>
test.js
import Vue from 'vue';
const test = {
state: {
obj: {}
},
mutations: {
updateMessage(state, message) {
state.obj = { message };
}
},
}
export default test;
以上代碼在嚴格模式下會報錯
vuex開啟嚴格模式, 僅需要在創建store的時候傳入 strict: true
const store = new Vuex.Store({
// ...
strict: true
})
在嚴格模式下, 無論何時發生了狀態變更且且不是由mutation函數引起的, 經會拋出錯誤, 這能保證所有的狀態變更都能被調試工具跟蹤到;
那我們應該怎么處理呢, vuex的官方文檔中給給了解決方法: 表單處理
- 給 <input> 中綁定 value,然后偵聽 input 或者 change 事件,在事件回調中調用 action:
<template>
<div >
<input type="text" :value="message" @input="updateMessage">
</div>
</template>
<script>
export default {
computed: {
message(){
return this.$store.state.test.obj.message;
},
},
methods: {
updateMessage(e) {
this.$store.commit('updateMessage', e.target.value)
},
}
}
</script>
- 雙向綁定的計算屬性: v-model + 使用帶有 setter 的雙向綁定計算屬性
<template>
<div >
<input type="text" v-model="message">
</div>
</template>
<script>
export default {
computed: {
message: {
get() {
return this.$store.state.test.obj.message;
},
set(value) {
this.$store.commit('updateMessage', value)
}
},
},
}
</script>