1. State
Vuex 使用單一狀態樹——用一個對象包含全部的應用層級狀態。至此它就是“唯一數據源 (SSOT)”。這也意味着,每個應用將僅僅包含一個 store 實例。
組件中獲得 Vuex 狀態
-
計算屬性
import store from './store' const Counter = { template: `<div>{{ count }}</div>`, computed: { count () { return store.state.count } } }
然而,組件可能需要頻繁導入store。
-
store選項
//父組件 import store from './store' import Counter from './Counter' new Vue({ el: '#app', store, //將store從根組件“注入”到每一個子組件中 render:c=>c(Counter), })
//子組件Counter.vue能通過 `this.$store` 訪問: <template> <div>{{count}}</div> </template> <script> export default { computed: { count () { return this.$store.state.count } } } </script>
輔助函數mapState
當一個組件需要獲取多個狀態的時候,將這些狀態都聲明為計算屬性會有些重復和冗余。為了解決這個問題,我們可以使用 mapState
輔助函數幫助我們生成計算屬性:
//store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex)
export default new Vuex.Store({
state:{
count:4
}
})
//Counter.vue
<template>
<div>{{sumCount}}</div>
</template>
<script>
import store from './store'
import {mapState} from 'vuex' //引入輔助函數
export default {
data(){
return{
localCount:7
}
},
computed:mapState({
count:state=>state.count, //返回count,等同於count () {return this.$store.state.count}
countAlias:'count', //count的別名是countAlias,這時候countAlias等同於count(注意單引號)
sumCount(state){ //處理多狀態:store的數據加上本地的數據
return state.count+this.localCount
}
})
}
</script>
當映射的計算屬性的名稱與 state 的子節點名稱相同時,可以給 mapState
傳一個字符串數組:
computed: mapState([
'count' //等同於count:state=>state.count,
])
2. Getter
前言
假設我們需要處理store中state,例如對列表進行過濾並計數
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
然而如果很多組件都需要用到這個計算屬性,那么我們只能復制這個函數,或者頻繁導入它。
Vuex 允許我們在 store 中定義“getter”,就像計算屬性一樣,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變才會被重新計算。
應用
1. 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
接受第二個參數getter
getters: { doneTodos: state => { return state.todos.filter(todo => todo.done) } doneTodosCount: (state, getters) => { return getters.doneTodos.length //通過getter訪問到doneTodos屬性 } }
2. 通過屬性訪問
//訪問
store.getters.doneTodos
store.getters.doneTodosCount
//組件中使用
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}
3. 通過方法訪問
讓 getter 返回一個函數,來實現給 getter 傳參。這對在 store 里的數組進行查詢時非常有用
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
//訪問
store.getters.getTodoById(2) //返回元素id與傳過去的id相等的元素
輔助函數mapGetters
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用對象展開運算符,將 getter 混入 computed 對象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
3. Mutation
簡單示例
更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。 mutation 類似於事件:每個 mutation 都有一個 事件類型 (type) 和 一個 回調函數 (handler)。它接受 state
作為第一個參數:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
//事件類型:increment;回調函數為后面部分;
increment (state) {
state.count++
}
}
})
想要調用該事件類型的回調,需要使用store.commit()
,並傳入字符串的事件類型
store.commit('increment')
提交載荷(Payload)
可以向 store.commit
傳入額外的參數,這些額外參數就是 mutation 的 載荷(payload)
mutations: {
increment (state, n) {
state.count += n
}
}
store.commit('increment', 10)
在大多數情況下,載荷應該是一個對象,這樣更易讀:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
提交方式
1. 沒有載荷
store.commit('事件類型')
2. 有載荷:
//一般寫法
store.commit('increment', 10)
//載荷為一個對象時
store.commit('事件類型', {
amount: 10
})
//載荷為一個對象時-對象風格寫法
store.commit({
type: '事件類型',
amount: 10
})
3. 組件中提交
this.$store.commit('事件類型')
mapMutations輔助函數
methods:{
//...
...mapMutations([
'increment', //相當於this.$store.commit('increment')
'incrementBy', //相當於`this.$store.commit('incrementBy', n)`
'incrementBy2' //相當於this.$store.commit('incrementBy2',{amount})
]),
...mapMutations({
add:'increment' //此時this.add相當於$store.commit('increment')
})
}
注意:Mutation 必須是同步函數
使用常量代替Mutation類型
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// 可以使用 ES2015 風格的計算屬性命名功能來使用一個常量作為函數名
[SOME_MUTATION] (state) {
// mutate state
}
}
})
4. Action
前言
Action 類似於 mutation,不同在於:
- Action 提交的是 mutation,而不是直接變更狀態。
- Action 可以包含任意異步操作。
簡單示例
//一個往數組添加隨機數的實例:
const store = new Vuex.Store({
state: {
msg: []
},
mutations: {
addMessage(state,msg){
state.msg.push(msg)
}
},
//一個異步操作:
actions: {
getMessages(context){
fetch('https://www.easy-mock.com/mock/5f4a32907e1a7f3146e313e7/api/getnum')
.then(res=>res.json())
.then(data=>{
context.commit('addMessage',data.data.number)
})
}
}
})
Action 函數接受一個與 store 實例具有相同方法和屬性的 context 對象,因此你可以在這個對象中獲取 state 和 getters或提交一個 mutation。
ES6中可以通過參數解構簡化代碼
actions: {
getMessages({commit}){
//...
commit('addMessage',data.data.number)
//...
}
}
分發Action
1. 普通分發
Action 通過 store.dispatch
方法觸發,
store.dispatch('getMessages')
2. 載荷分發
// 普通
store.dispatch('事件類型', {
amount: 1
})
// 對象形式
store.dispatch({
type: '事件類型',
amount: 1
})
3. 組件中分發
this.$store.dispatch('事件類型')
mapMutations輔助函數
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'getMessages', // 相當於`this.$store.dispatch('getMessages')`
]),
...mapActions({
addmsg: 'getMessages' //this.addmsg等同於`this.$store.dispatch('getMessages')`
})
}
}
組合Action
如何知道 action 什么時候結束呢?如何才能組合多個 action,以處理更加復雜的異步流程呢?首先要知道:
store.dispatch
可以處理被觸發的 action 的處理函數返回的 Promisestore.dispatch
仍舊返回 Promise:
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
現在可以指定回調:
store.dispatch('actionA').then(() => {
// ...
})
也可以 另一個action指定回調:
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
最后利用 async / await,我們可以如下組合 action:
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
5. 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 的狀態
模塊的局部狀態
const moduleA = {
state: () => ({
count: 0
}),
//1. 對於mutation,接收的第一個參數是模塊的局部狀態對象。
mutations: {
increment (state) {
state.count++
}
},
//2. 對於getter,第一個參數暴露局部狀態;第三個參數`rootState`暴露根節點狀態
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
//3. 對於action,局部狀態通過 context.state 暴露出來,根節點通過 context.rootState:
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
命名空間
默認情況下,模塊內部的 action、mutation 和 getter 是注冊在全局命名空間的——這樣使得多個模塊能夠對同一 mutation 或 action 作出響應。
如果希望你的模塊具有更高的封裝度和復用性,你可以在模塊對象中加入 namespaced: true
使其成為帶命名空間的模塊。當模塊被注冊后,它的所有 getter、action 及 mutation 都會自動根據模塊注冊的路徑調整命名
const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
state: () => ({ ... }),
getters: {isAdmin () { ... }},// 通過 store.getters['account/isAdmin']訪問
actions: {login () { ... }},// 通過 store.dispatch('account/login')分發
mutations: {login () { ... }},//通過store.commit('account/login')提交
//模塊中嵌套模塊-繼承父模塊的命名空間
modules: {
//模塊1-myPage
myPage: {
state: () => ({ ... }),
getters: {profile () { ... }}//store.getters['account/profile']
},
//模塊2-mposts-進一步嵌套命名空間
posts: {
namespaced: true,
state: () => ({ ... }),
getters: {popular () { ... }}//store.getters['account/posts/popular']
}
}
}
}
})
在帶命名空間的模塊內訪問全局內容
rootState
和 rootGetters
會作為第三和第四參數傳入 getter,也會通過 context
對象的屬性傳入 action。
若需要在全局命名空間內分發 action 或提交 mutation,將 { root: true }
作為第三參數傳給 dispatch
或 commit
即可。
modules: {
foo: {
namespaced: true,
getters: {
someOtherGetter: state => { ... }
//1. 對於getter,使用第四個參數 `rootGetters`訪問根getter
someGetter (state, getters, rootState, rootGetters) {
getters.someOtherGetter // -> 局部的
rootGetters.someOtherGetter // -> 全局的
},
},
actions: {
someOtherAction (ctx, payload) { ... }
//2. 對於actions,接受 root 屬性來訪問根 dispatch 或 commit
someAction ({ dispatch, commit, getters, rootGetters }) {
dispatch('someOtherAction') // -> 局部的分發
dispatch('someOtherAction', null, { root: true }) // -> 全局的分發
commit('someMutation') // -> 局部的提交
commit('someMutation', null, { root: true }) // -> 全局的提交
},
}
}
}
在帶命名空間的模塊注冊全局 action
在action中添加 root: true
,並將這個 action 的定義放在函數 handler
中。例如:
{
//...
modules: {
foo: {
namespaced: true,
actions: {
someAction: {
root: true,
handler (namespacedContext, payload) { ... } // -> 定義someAction action
}
}
}
}
}
帶命名空間的綁定函數
當使用 mapState
, mapGetters
, mapActions
和 mapMutations
這些函數來綁定帶命名空間的模塊時:
computed: {
...mapState({
a: state => state.some.nested.module.a,
b: state => state.some.nested.module.b
})
},
methods: {
...mapActions([
'some/nested/module/foo', // -> this['some/nested/module/foo']()
'some/nested/module/bar' // -> this['some/nested/module/bar']()
])
}
你可以將模塊的空間名稱字符串作為第一個參數傳遞給上述函數,這樣所有綁定都會自動將該模塊作為上下文,簡化后:
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActions('some/nested/module', [
'foo', // -> this.foo()
'bar' // -> this.bar()
])
}
簡化的另一個方法:使用 createNamespacedHelpers
創建基於某個命名空間輔助函數。它返回一個對象,對象里有新的綁定在給定命名空間值上的組件綁定輔助函數:
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')//基於某個命名
export default {
computed: {
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActions([
'foo',
'bar'
])
}
}