一扭粱、概念
每一個 Vuex 應用的核心就是 store(倉庫)慌盯,它包含著你的應用中大部分的狀態(tài) (state)瓢省。Vuex 和單純的全局對象有以下兩點不同。
- Vuex 的狀態(tài)存儲是響應式的。若 store 中的狀態(tài)發(fā)生變化憋沿,那么相應的組件也會相應地得到高效更新。
- 不能直接改變 store 中的狀態(tài)。改變 store 中的狀態(tài)的唯一途徑就是顯式地提交 (commit) mutation锻狗。
簡單的設置一個vue的store:
// 如果在模塊化構建系統(tǒng)中,請確保在開頭調用了 Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
store.commit('increment') //使用改變屬性的方法
console.log(store.state.count) // -> 1 直接獲取屬性的值
二焕参、State
屬性存放位置
Vuex 使用單一狀態(tài)樹轻纪,作為一個“唯一數(shù)據(jù)源而存在。由于 Vuex 的狀態(tài)存儲是響應式的龟糕,從 store 實例中讀取狀態(tài)最簡單的方法就是在計算屬性中返回某個狀態(tài)桐磁。
// 創(chuàng)建一個 Counter 組件
const Counter = {
template: `<div>{{ count }}</div>`,
computed: { //計算屬性
count () {
return store.state.count
}
}
}
//每當 store.state.count 變化的時候, 都會重新求取計算屬性,并且觸發(fā)更新相關聯(lián)的 DOM讲岁。
這種模式導致組件依賴全局狀態(tài)單例我擂,在模塊化的構建系統(tǒng)中,在每個需要使用 state 的組件中需要頻繁地導入缓艳,并且在測試組件時需要模擬狀態(tài)校摩。
mapState 輔助函數(shù)
當一個組件需要獲取多個狀態(tài)時候,將這些狀態(tài)都聲明為計算屬性會有些重復和冗余阶淘。此時可以使用 mapState 輔助函數(shù)幫助我們生成計算屬性衙吩。
- 寫法一:對象形式
// 在單獨構建的版本中輔助函數(shù)為 Vuex.mapState
import { mapState } from 'vuex'
export default {
computed: mapState({
count: state => state.count,
countAlias: 'count', // 傳字符串參數(shù) 'count' 等同于 `state => state.count`
countPlusLocalState (state) { // 為了能夠使用 `this` 獲取局部狀態(tài),必須使用常規(guī)函數(shù)
return state.count + this.localCount
}
})
}
- 寫法二:數(shù)組形式(當映射的計算屬性的名稱與 state 的子節(jié)點名稱相同時)
computed: mapState(['count'])
對象展開運算符
如何將它與局部計算屬性混合使用溪窒?需要使用一個工具函數(shù)將多個對象合并為一個坤塞,以使我們可以將最終對象傳給 computed 屬性。
因此有了對象展開運算符澈蚌,我們可以極大地簡化原有的寫法:
import {mapState} from 'vuex'
computed: {
localComputed () { /* ... */ }, //局部計算屬性
// 使用對象展開運算符將此對象混入到外部對象中
...mapState({
count: state => state.countModel.count, //可以有多個模塊文件
})
//數(shù)組形式
...mapState(['count'])
}
組件仍然保有局部狀態(tài)
雖然將所有的狀態(tài)放到 Vuex 會使狀態(tài)變化更顯式和易調試摹芙,但也會使代碼變得冗長和不直觀。如果有些狀態(tài)嚴格屬于單個組件宛瞄,最好還是作為組件的局部狀態(tài)浮禾。你應該根據(jù)你的應用開發(fā)需要進行權衡和確定。
二份汗、Getter
獲取屬性值
Vuex 允許我們在 store 中定義“getter”(可以認為是 store 的計算屬性)盈电。getter 的返回值會根據(jù)它的依賴被緩存起來,且只有當它的依賴值發(fā)生了改變才會被重新計算杯活。
Getter
通過屬性訪問
- Getter 會暴露為 store.getters 對象匆帚,你可以以屬性的形式訪問這些值:
store.getters.count
- 接受 state 作為其第一個參數(shù),也可以接受其他 getter 作為第二個參數(shù)
getters: {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length //getters是獲取的對象
}
}
通過方法訪問
- 可以通過讓 getter 返回一個函數(shù)旁钧,來實現(xiàn)給 getter 傳參卷扮。
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
//使用
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
mapGetters 輔助函數(shù)
將 store 中的 getter 映射到局部計算屬性:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用對象展開運算符將 getter 混入 computed 對象中
...mapGetters([
'doneTodosCount',
'getTodoById',
doneCount: 'doneTodosCount' //重新取一個名字
// ...
])
}
}
三荡澎、Mutation
更改 Vuex 的 store 中的狀態(tài)的唯一方法是提交 mutation。
vuex中mutation都類似于事件:每一個mutation都有一個字符串的類型type和一個回調函數(shù)晤锹,回調函數(shù)即為我們實際修改屬性值狀態(tài)更改的地方摩幔,接受 state 作為第一個參數(shù):
export default{
mutations: {
increment (state) {
state.count++; // 變更狀態(tài)
}
}
}
不能直接調用一個 mutation的回調函數(shù),該選項更像事件注冊鞭铆,觸發(fā)increment類型的mutation時或衡,會直接調用改類型下的回調函數(shù),需要相應的type調用store.commit:
store.commit('increment')
提交載荷(Payload)
可以向 store.commit 傳入額外的參數(shù)车遂,即 mutation 的 載荷(payload)
mutations: {
increment (state, n) {
state.count += n
}
}
//使用
store.commit('increment', 10)
大多數(shù)情況下封断,載荷應該是一個對象,這樣可以包含多個字段并且記錄的 mutation 會更易讀
store.commit('increment', {
amount: 10
})
對象風格的提交方式
提交 mutation 可以直接使用包含 type 屬性的對象:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
//對象風格的提交方式
store.commit({
type: 'increment',
amount: 10
})
Mutation 需遵守 Vue 的響應規(guī)則
因為Vuex 的 store 中的狀態(tài)是響應式的舶担,因此變更狀態(tài)時坡疼,監(jiān)視狀態(tài)的 Vue 組件也會自動更新
- 最好提前在你的 store 中初始化好所有所需屬性。
- 當需要在對象上添加新屬性時衣陶,你應該
- 使用 Vue.set(obj, 'newProp', 123), 或者
- 以新對象替換老對象
以上寫法state.obj = { ...state.obj, newProp: 123 } //添加一個對象
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; x; // 1 y; // 2 z; // { a: 3, b: 4 }
使用常量替代 Mutation 事件類型
可以使 linter 之類的工具發(fā)揮作用柄瑰,同時把這些常量放在單獨的文件中可以對整個 app 包含的 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 風格的計算屬性命名功能來使用一個常量作為函數(shù)名
[SOME_MUTATION] (state) {
// mutate state
}
}
})
在組件中提交 Mutation
- 在組件中使用以下代碼提交
this.$store.commit('類型')
- mapMutations 輔助函數(shù)
import { mapMutations } from 'vuex'
export default {
methods: {
...mapMutations([ // `mapMutations` 也支持載荷:
'increment',
'incrementBy'
]),
...mapMutations({ //重新取名字
add: 'increment'
})
}
}
//使用
this.increment()教沾; //映射為 this.$store.commit('increment')
this.incrementBy(amount); //映射為this.$store.commit('incrementBy', amount)
this.add(); //映射為this.$store.commit('increment')
Mutation 必須是同步函數(shù)
==堅決不能是異步函數(shù)== , mutation 都是同步事務
在 mutation 中混合異步調用會導致你的程序很難調試译断。當你調用了兩個包含異步回調的 mutation 來改變狀態(tài)授翻,你怎么知道什么時候回調和哪個先回調呢?
四孙咪、Action
Action 類似于 mutation堪唐,區(qū)別:
- Action 提交的是 mutation,而不是直接變更狀態(tài)翎蹈。
- Action 可以包含任意異步操作羔杨。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
Action 函數(shù)接受一個與 store 實例具有相同方法和屬性的 context 對象,可以調用 context.commit 提交一個 mutation,context.state 和 context.getters 來獲取 state 和 getters杨蛋,可以用ES2015 的參數(shù)解構increment ({commit,state,getters})
分發(fā) Action
Action 通過 store.dispatch 方法觸發(fā):
store.dispatch('increment')
可以在 action 內部執(zhí)行異步操作,支持同樣的載荷方式和對象方式進行分發(fā):
// 以載荷形式分發(fā)
store.dispatch('increment', {
amount: 10
})
// 以對象形式分發(fā)
store.dispatch({
type: 'increment',
amount: 10
})
store.dispatch('increment',params)
在組件中分發(fā) Action
- 直接使用dispatch分發(fā)
this.$store.dispatch('xxx')
- 使用mapActions 輔助函數(shù)
import { mapActions } from 'vuex' export default { methods: { ...mapActions(['increment', 'incrementBy' ]), ...mapActions({ add: 'increment' }) } mounted() { this.increment(); // 映射為this.$store.dispatch('increment') this.incrementBy(amount); //映射為this.$store.dispatch('incrementBy', amount) this.add(); //映射為this.$store.dispatch('increment') } }
組合 Action
store.dispatch 可以處理被觸發(fā)的 action 的處理函數(shù)返回的 Promise,并且 store.dispatch 仍舊返回 Promiseactions: { actionA ({ commit }) { return new Promise((resolve, reject) => { setTimeout(() => { commit('someMutation') resolve() }, 1000) }) }, actionB ({ dispatch, commit }) { return dispatch('actionA').then(() => { commit('someOtherMutation') }) } }
- 可以利用 async / await
// 假設 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 分割成模塊。每個模塊擁有自己的 state糠爬、mutation寇荧、action、getter执隧、甚至是嵌套子模塊——從上至下進行同樣方式的分割:
// moduleA.js
export default moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
//store.js
import Vue from 'vue'
import Vuex from 'vuex'
import moduleA from './moduleA'
Vue.use(Vuex)
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
export default new Vuex.Store({
modules: {
moduleA,
b: moduleB
}
})
- 對于模塊內部的 mutation 和 getter揩抡,接收的第一個參數(shù)是模塊的局部狀態(tài)對象户侥。
- 對于模塊內部的 action,局部狀態(tài)通過 context.state 暴露出來;根節(jié)點狀態(tài)則為 context.rootState
- 對于模塊內部的 getter,根節(jié)點狀態(tài)rootState會作為第三個參數(shù)暴露出
命名空間
[復雜]
帶命名空間的綁定函數(shù)
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
})
},
-
你可以通過使用 createNamespacedHelpers 創(chuàng)建基于某個命名空間輔助函數(shù)峦嗤,返回一個對象蕊唐,對象里有新的綁定在給定命名空間值上的組件綁定輔助函數(shù):
import { createNamespacedHelpers } from 'vuex' const { mapState, mapActions } = createNamespacedHelpers('some/nested/module') export default { computed: { ...mapState({ a: state => state.a, b: state => state.b }) } }
后面會更新具體在項目中的使用