- state: 最底層的初始數(shù)據(jù)
- getters: 相當(dāng)于vue的計(jì)算屬性旅东,對(duì)state數(shù)據(jù)進(jìn)行處理 序芦、擴(kuò)展
- mutations: 當(dāng)需要修改state時(shí)靡狞,在這里定義mutations
- actions: 當(dāng)需要對(duì)多個(gè)mutations進(jìn)行處理時(shí)褪那,在actions進(jìn)行mutations派發(fā)半醉,異步處理也是在這里定義
這是使用方法疚俱、后面為介紹:
// 簡(jiǎn)單使用 定義目錄 src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import createLogger from 'vuex/dist/logger'
Vue.use(Vuex)
// 開(kāi)啟變化顯示
const debug = process.env.NODE_ENV !== 'production'
const state = {
name: 'zfx'
}
const getters = {}
const mutations = {
set_name(state, data) {
state.name = data
}
}
const actions = {}
export default new Vuex.Store({
state,
getters,
mutations,
actions,
strict: debug,
plugins: debug ? [createLogger()] : []
})
// 使用目錄 src/pages/index.vue
import { mapState, mutations } from 'vuex'
...
// state、getters 在computed計(jì)算屬性引入
// mutations缩多、 actions在mounted引入 格式如下:
mounted() {
...mutations([
'set_name'
])
}
...
Vuex 是一個(gè)專為 Vue.js 應(yīng)用程序開(kāi)發(fā)的狀態(tài)管理模式呆奕。它采用集中式存儲(chǔ)管理應(yīng)用的所有組件的狀態(tài)养晋,并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測(cè)的方式發(fā)生變化。
什么是“狀態(tài)管理模式”梁钾?
讓我們從一個(gè)簡(jiǎn)單的 Vue 計(jì)數(shù)應(yīng)用開(kāi)始:
new Vue({
// state 初始狀態(tài)(數(shù)據(jù))
data () {
return {
count: 0
}
},
// view 視圖
template: `
<div>{{ count }}</div>
`,
// actions 變化(模型)
methods: {
increment () {
this.count++
}
}
})
這個(gè)狀態(tài)自管理應(yīng)用包含以下幾個(gè)部分:
- state绳泉,驅(qū)動(dòng)應(yīng)用的數(shù)據(jù)源;
- view陈轿,以聲明方式將 state 映射到視圖圈纺;
- actions,響應(yīng)在 view 上的用戶輸入導(dǎo)致的狀態(tài)變化麦射。
以下是一個(gè)表示“單向數(shù)據(jù)流”理念的極簡(jiǎn)示意:
[圖片上傳失敗...(image-46465a-1514178818777)]
但是,當(dāng)我們的應(yīng)用遇到多個(gè)組件共享狀態(tài)時(shí)灯谣,單向數(shù)據(jù)流的簡(jiǎn)潔性很容易被破壞:
- 多個(gè)視圖依賴于同一狀態(tài)潜秋。
- 來(lái)自不同視圖的行為需要變更同一狀態(tài)。
對(duì)于問(wèn)題一胎许,傳參的方法對(duì)于多層嵌套的組件將會(huì)非常繁瑣峻呛,并且對(duì)于兄弟組件間的狀態(tài)傳遞無(wú)能為力。對(duì)于問(wèn)題二辜窑,我們經(jīng)常會(huì)采用父子組件直接引用或者通過(guò)事件來(lái)變更和同步狀態(tài)的多份拷貝钩述。以上的這些模式非常脆弱,通常會(huì)導(dǎo)致無(wú)法維護(hù)的代碼穆碎。
因此牙勘,我們?yōu)槭裁床话呀M件的共享狀態(tài)抽取出來(lái),以一個(gè)全局單例模式管理呢所禀?在這種模式下方面,我們的組件樹(shù)構(gòu)成了一個(gè)巨大的“視圖”,不管在樹(shù)的哪個(gè)位置色徘,任何組件都能獲取狀態(tài)或者觸發(fā)行為恭金!
另外,通過(guò)定義和隔離狀態(tài)管理中的各種概念并強(qiáng)制遵守一定的規(guī)則褂策,我們的代碼將會(huì)變得更結(jié)構(gòu)化且易維護(hù)横腿。
這就是 Vuex 背后的基本思想,借鑒了
The Elm Architecture。與其他模式不同的是扬蕊,Vuex 是專門(mén)為 Vue.js 設(shè)計(jì)的狀態(tài)管理庫(kù)搀别,以利用 Vue.js 的細(xì)粒度數(shù)據(jù)響應(yīng)機(jī)制來(lái)進(jìn)行高效的狀態(tài)更新。
開(kāi)始
每一個(gè) Vuex 應(yīng)用的核心就是 store(倉(cāng)庫(kù)),“store”基本上就是一個(gè)容器尾抑,它包含著你的應(yīng)用中大部分的狀態(tài) (state)歇父。Vuex 和單純的全局對(duì)象有以下兩點(diǎn)不同:
Vuex 的狀態(tài)存儲(chǔ)是響應(yīng)式的蒂培。當(dāng) Vue 組件從 store 中讀取狀態(tài)的時(shí)候,若 store 中的狀態(tài)發(fā)生變化榜苫,那么相應(yīng)的組件也會(huì)相應(yīng)地得到高效更新护戳。
你不能直接改變 store 中的狀態(tài)。改變 store 中的狀態(tài)的唯一途徑就是顯式地提交 mutation(變化)垂睬。這樣使得我們可以方便地跟蹤每一個(gè)狀態(tài)的變化媳荒,從而讓我們能夠?qū)崿F(xiàn)一些工具幫助我們更好地了解我們的應(yīng)用。
最簡(jiǎn)單的 Store
僅需要提供一個(gè)初始 state 對(duì)象和一些 mutation:
// 如果在模塊化構(gòu)建系統(tǒng)中驹饺,請(qǐng)確保在開(kāi)頭調(diào)用了
Vue.use(Vuex)
const store = new Vuex.Store({
state: { //狀態(tài)
count: 0
},
mutations: { //變化
increment (state) {
state.count++
}
}
})
現(xiàn)在钳枕,你可以通過(guò) store.state 來(lái)獲取狀態(tài)對(duì)象,以及通過(guò) store.commit 方法觸發(fā)狀態(tài)變更:
console.log(store.state.count) // -> 1
store.commit('increment')
再次強(qiáng)調(diào)赏壹,我們通過(guò)提交 mutation 的方式鱼炒,而非直接改變 store.state.count,是因?yàn)槲覀兿胍鞔_地追蹤到狀態(tài)的變化蝌借。這個(gè)簡(jiǎn)單的約定能夠讓你的意圖更加明顯昔瞧,這樣你在閱讀代碼的時(shí)候能更容易地解讀應(yīng)用內(nèi)部的狀態(tài)改變。此外菩佑,這樣也讓我們有機(jī)會(huì)去實(shí)現(xiàn)一些能記錄每次狀態(tài)改變自晰,保存狀態(tài)快照的調(diào)試工具。有了它稍坯,我們甚至可以實(shí)現(xiàn)如時(shí)間穿梭般的調(diào)試體驗(yàn)酬荞。
由于 store 中的狀態(tài)是響應(yīng)式的,在組件中調(diào)用 store 中的狀態(tài)簡(jiǎn)單到僅需要在計(jì)算屬性中返回即可劣光。觸發(fā)變化也僅僅是在組件的 methods 中提交 mutation袜蚕。
核心概念
State
Vuex 使用單一狀態(tài)樹(shù)——是的,用一個(gè)對(duì)象就包含了全部的應(yīng)用層級(jí)狀態(tài)绢涡。至此它便作為一個(gè)“唯一數(shù)據(jù)源 (SSOT)”而存在牲剃。這也意味著,每個(gè)應(yīng)用將僅僅包含一個(gè) store 實(shí)例雄可。單一狀態(tài)樹(shù)讓我們能夠直接地定位任一特定的狀態(tài)片段凿傅,在調(diào)試的過(guò)程中也能輕易地取得整個(gè)當(dāng)前應(yīng)用狀態(tài)的快照。
Vuex 通過(guò) store 選項(xiàng)数苫,提供了一種機(jī)制將狀態(tài)從根組件“注入”到每一個(gè)子組件中(需調(diào)用 Vue.use(Vuex)):
Vue.use(Vuex)
const app = new Vue({
el: '#app',
// 把 store 對(duì)象提供給 “store” 選項(xiàng)聪舒,這可以把 store 的實(shí)例注入所有的子組件
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})
通過(guò)在根實(shí)例中注冊(cè) store 選項(xiàng),該 store 實(shí)例會(huì)注入到根組件下的所有子組件中虐急,且子組件能通過(guò) this.$store 訪問(wèn)到箱残。讓我們更新下 Counter 的實(shí)現(xiàn):
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
mapState 輔助函數(shù)
當(dāng)一個(gè)組件需要獲取多個(gè)狀態(tài)時(shí)候,將這些狀態(tài)都聲明為計(jì)算屬性會(huì)有些重復(fù)和冗余。為了解決這個(gè)問(wèn)題被辑,我們可以使用 mapState 輔助函數(shù)幫助我們生成計(jì)算屬性燎悍,讓你少按幾次鍵:
// 在單獨(dú)構(gòu)建的版本中輔助函數(shù)為 Vuex.mapState
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
// 箭頭函數(shù)可使代碼更簡(jiǎn)練
count: state => state.count,
// 傳字符串參數(shù) 'count' 等同于 `state => state.count`
countAlias: 'count',
// 為了能夠使用 `this` 獲取局部狀態(tài),必須使用常規(guī)函數(shù)
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
當(dāng)映射的計(jì)算屬性的名稱與 state 的子節(jié)點(diǎn)名稱相同時(shí)盼理,我們也可以給 mapState 傳一個(gè)字符串?dāng)?shù)組谈山。
computed: mapState([
// 映射 this.count 為 store.state.count
'count'
])
對(duì)象展開(kāi)運(yùn)算符
mapState
函數(shù)返回的是一個(gè)對(duì)象。我們?nèi)绾螌⑺c局部計(jì)算屬性混合使用呢宏怔?通常奏路,我們需要使用一個(gè)工具函數(shù)將多個(gè)對(duì)象合并為一個(gè),以使我們可以將最終對(duì)象傳給computed
屬性臊诊。但是自從有了對(duì)象展開(kāi)運(yùn)算符(現(xiàn)處于 ECMASCript 提案 stage-3 階段)鸽粉,我們可以極大地簡(jiǎn)化寫(xiě)法:
computed: {
// 使用對(duì)象展開(kāi)運(yùn)算符將此對(duì)象混入到外部對(duì)象中
...mapState({
'count'
})
}
組件仍然保有局部狀態(tài)
使用 Vuex 并不意味著你需要將所有的狀態(tài)放入 Vuex。雖然將所有的狀態(tài)放到 Vuex 會(huì)使?fàn)顟B(tài)變化更顯式和易調(diào)試妨猩,但也會(huì)使代碼變得冗長(zhǎng)和不直觀潜叛。如果有些狀態(tài)嚴(yán)格屬于單個(gè)組件,最好還是作為組件的局部狀態(tài)壶硅。你應(yīng)該根據(jù)你的應(yīng)用開(kāi)發(fā)需要進(jìn)行權(quán)衡和確定。
Getter
有時(shí)候我們需要從 store 中的 state 中派生出一些狀態(tài)销斟,例如對(duì)列表進(jìn)行過(guò)濾并計(jì)數(shù):
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
如果有多個(gè)組件需要用到此屬性庐椒,我們要么復(fù)制這個(gè)函數(shù),或者抽取到一個(gè)共享函數(shù)然后在多處導(dǎo)入它——無(wú)論哪種方式都不是很理想蚂踊。
Vuex 允許我們?cè)?store 中定義“getter”(可以認(rèn)為是 store 的計(jì)算屬性)约谈。就像計(jì)算屬性一樣,getter 的返回值會(huì)根據(jù)它的依賴被緩存起來(lái)犁钟,且只有當(dāng)它的依賴值發(fā)生了改變才會(huì)被重新計(jì)算棱诱。
Getter 接受 state 作為其第一個(gè)參數(shù):
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 會(huì)暴露為 store.getters 對(duì)象:
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
Getter 也可以接受其他 getter 作為第二個(gè)參數(shù):
getters: {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
store.getters.doneTodosCount // -> 1
你也可以通過(guò)讓 getter 返回一個(gè)函數(shù),來(lái)實(shí)現(xiàn)給 getter 傳參涝动。在你對(duì) store 里的數(shù)組進(jìn)行查詢時(shí)非常有用迈勋。
getters: {
// ...
getTodoById: (state) => (id) => {
// 過(guò)濾器 返回id === 2的數(shù)據(jù)
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
mapGetters 輔助函數(shù)
mapGetters 輔助函數(shù)僅僅是將 store 中的 getter 映射到局部計(jì)算屬性:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用對(duì)象展開(kāi)運(yùn)算符將 getter 混入 computed 對(duì)象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
如果你想將一個(gè) getter 屬性另取一個(gè)名字,使用對(duì)象形式:
mapGetters({
// 映射 `this.doneCount` 為 `store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
Mutation同步事務(wù)
更改 Vuex 的 store 中的狀態(tài)的唯一方法是提交 mutation醋粟。
提交載荷(Payload)
你可以向 store.commit 傳入額外的參數(shù)靡菇,即 mutation 的 載荷
mutations: {
increment (state, n) {
state.count += n
}
}
store.commit('increment', 10)
在大多數(shù)情況下,載荷應(yīng)該是一個(gè)對(duì)象米愿,這樣可以包含多個(gè)字段并且記錄的 mutation 會(huì)更易讀:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
可以這樣創(chuàng)建
//創(chuàng)建
export default new Vuex.Store({
mutations
})
//接收
...mapMutations([
'set_audio_data', // 設(shè)置audio數(shù)據(jù)
'set_playMode', // 設(shè)置播放模式
'set_playList' // 設(shè)置播放列表數(shù)據(jù)
]),
如果這樣創(chuàng)建Vuex, 可以用
this.set_playMode(val) 調(diào)用
Mutation 需遵守 Vue 的響應(yīng)規(guī)則
既然 Vuex 的 store 中的狀態(tài)是響應(yīng)式的厦凤,那么當(dāng)我們變更狀態(tài)時(shí),監(jiān)視狀態(tài)的 Vue 組件也會(huì)自動(dòng)更新育苟。這也意味著 Vuex 中的 mutation 也需要與使用 Vue 一樣遵守一些注意事項(xiàng):
最好提前在你的 store 中初始化好所有所需屬性较鼓。
當(dāng)需要在對(duì)象上添加新屬性時(shí),你應(yīng)該
使用
Vue.set(obj, 'newProp', 123)
, 或者-
以新對(duì)象替換老對(duì)象违柏。例如博烂,利用 stage-3 的對(duì)象展開(kāi)運(yùn)算符我們可以這樣寫(xiě):
如:let x1 = { a: 1, b: 2 }; x1 = { ...x1, c: 3 }; 現(xiàn)在 x1 為 { a: 1, b: 2, c: 3 }; state.obj = { ...state.obj, newProp: 123 }
Mutation 必須是同步函數(shù)
不能使用回調(diào)函數(shù)
在組件中提交 Mutation
你可以在組件中使用 this.$store.commit('xxx') 提交 mutation香椎,或者使用 mapMutations 輔助函數(shù)將組件中的 methods 映射為 store.commit 調(diào)用(需要在根節(jié)點(diǎn)注入 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)`
]),
this.incrementBy(); 調(diào)用
...mapMutations({
add: 'increment' // 將 `this.add()` 映射為 `this.$store.commit('increment')`
})
}
}
Action異步操作
Action 類似于 mutation脖母,不同在于:
- Action 提交的是 mutation
變化
士鸥,而不是直接變更狀態(tài)。 - Action 可以包含任意異步操作谆级。
讓我們來(lái)注冊(cè)一個(gè)簡(jiǎn)單的 action:用到了 ES2015 的參數(shù)解構(gòu) 來(lái)簡(jiǎn)化代碼
actions: {
increment ({ commit }) {
commit('increment') //commit提交mutation
}
}
分發(fā) Action
Action 通過(guò) store.dispatch 方法觸發(fā):
store.dispatch('increment')
Actions 支持同樣的載荷方式和對(duì)象方式進(jìn)行分發(fā):
// 以載荷形式分發(fā)
store.dispatch('incrementAsync', {
amount: 10
})
// 以對(duì)象形式分發(fā)
store.dispatch({
type: 'incrementAsync',
amount: 10
})
async和await用法
- 只有在async方法里面才能使用await操作符烤礁;
- await操作符是針對(duì)Task對(duì)象的;
- 當(dāng)方法A調(diào)用方法B,方法B方法體內(nèi)又通過(guò)await調(diào)用方法C時(shí)肥照,如果方法C內(nèi)部有異步操作脚仔,則方法B會(huì)等待異步操作執(zhí)行完,才往下執(zhí)行舆绎;但方法A可以繼續(xù)往下執(zhí)行鲤脏,不用再等待B方法執(zhí)行完。
在組件中分發(fā) Action
使用 mapActions 輔助函數(shù)將組件的 methods 映射為 store.dispatch 調(diào)用(需要先在根節(jié)點(diǎn)注入 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 什么時(shí)候結(jié)束呢嗜诀?更重要的是凳怨,我們?nèi)绾尾拍芙M合多個(gè) action,以處理更加復(fù)雜的異步流程?
首先玄货,你需要明白 store.dispatch 可以處理被觸發(fā)的 action 的處理函數(shù)返回的 Promise姻僧,并且 store.dispatch 仍舊返回 Promise:
// 假設(shè) getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成,再繼續(xù)執(zhí)行
commit('gotOtherData', await getOtherData())
}
}
一個(gè) store.dispatch 在不同模塊中可以觸發(fā)多個(gè) action 函數(shù)占业。在這種情況下侵蒙,只有當(dāng)所有觸發(fā)函數(shù)完成后,返回的 Promise 才會(huì)執(zhí)行第队。