- state: 最底層的初始數據
- getters: 相當于vue的計算屬性,對state數據進行處理 、擴展
- mutations: 當需要修改state時,在這里定義mutations
- actions: 當需要對多個mutations進行處理時,在actions進行mutations派發,異步處理也是在這里定義
這是使用方法、后面為介紹:
// 簡單使用 定義目錄 src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import createLogger from 'vuex/dist/logger'
Vue.use(Vuex)
// 開啟變化顯示
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計算屬性引入
// mutations、 actions在mounted引入 格式如下:
mounted() {
...mutations([
'set_name'
])
}
...
Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化。
什么是“狀態管理模式”?
讓我們從一個簡單的 Vue 計數應用開始:
new Vue({
// state 初始狀態(數據)
data () {
return {
count: 0
}
},
// view 視圖
template: `
<div>{{ count }}</div>
`,
// actions 變化(模型)
methods: {
increment () {
this.count++
}
}
})
這個狀態自管理應用包含以下幾個部分:
- state,驅動應用的數據源;
- view,以聲明方式將 state 映射到視圖;
- actions,響應在 view 上的用戶輸入導致的狀態變化。
以下是一個表示“單向數據流”理念的極簡示意:
[圖片上傳失敗...(image-46465a-1514178818777)]
但是,當我們的應用遇到多個組件共享狀態時,單向數據流的簡潔性很容易被破壞:
- 多個視圖依賴于同一狀態。
- 來自不同視圖的行為需要變更同一狀態。
對于問題一,傳參的方法對于多層嵌套的組件將會非常繁瑣,并且對于兄弟組件間的狀態傳遞無能為力。對于問題二,我們經常會采用父子組件直接引用或者通過事件來變更和同步狀態的多份拷貝。以上的這些模式非常脆弱,通常會導致無法維護的代碼。
因此,我們為什么不把組件的共享狀態抽取出來,以一個全局單例模式管理呢?在這種模式下,我們的組件樹構成了一個巨大的“視圖”,不管在樹的哪個位置,任何組件都能獲取狀態或者觸發行為!
另外,通過定義和隔離狀態管理中的各種概念并強制遵守一定的規則,我們的代碼將會變得更結構化且易維護。
這就是 Vuex 背后的基本思想,借鑒了
The Elm Architecture。與其他模式不同的是,Vuex 是專門為 Vue.js 設計的狀態管理庫,以利用 Vue.js 的細粒度數據響應機制來進行高效的狀態更新。
開始
每一個 Vuex 應用的核心就是 store(倉庫),“store”基本上就是一個容器,它包含著你的應用中大部分的狀態 (state)。Vuex 和單純的全局對象有以下兩點不同:
Vuex 的狀態存儲是響應式的。當 Vue 組件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那么相應的組件也會相應地得到高效更新。
你不能直接改變 store 中的狀態。改變 store 中的狀態的唯一途徑就是顯式地提交 mutation(變化)。這樣使得我們可以方便地跟蹤每一個狀態的變化,從而讓我們能夠實現一些工具幫助我們更好地了解我們的應用。
最簡單的 Store
僅需要提供一個初始 state 對象和一些 mutation:
// 如果在模塊化構建系統中,請確保在開頭調用了
Vue.use(Vuex)
const store = new Vuex.Store({
state: { //狀態
count: 0
},
mutations: { //變化
increment (state) {
state.count++
}
}
})
現在,你可以通過 store.state 來獲取狀態對象,以及通過 store.commit 方法觸發狀態變更:
console.log(store.state.count) // -> 1
store.commit('increment')
再次強調,我們通過提交 mutation 的方式,而非直接改變 store.state.count,是因為我們想要更明確地追蹤到狀態的變化。這個簡單的約定能夠讓你的意圖更加明顯,這樣你在閱讀代碼的時候能更容易地解讀應用內部的狀態改變。此外,這樣也讓我們有機會去實現一些能記錄每次狀態改變,保存狀態快照的調試工具。有了它,我們甚至可以實現如時間穿梭般的調試體驗。
由于 store 中的狀態是響應式的,在組件中調用 store 中的狀態簡單到僅需要在計算屬性中返回即可。觸發變化也僅僅是在組件的 methods 中提交 mutation。
核心概念
State
Vuex 使用單一狀態樹——是的,用一個對象就包含了全部的應用層級狀態。至此它便作為一個“唯一數據源 (SSOT)”而存在。這也意味著,每個應用將僅僅包含一個 store 實例。單一狀態樹讓我們能夠直接地定位任一特定的狀態片段,在調試的過程中也能輕易地取得整個當前應用狀態的快照。
Vuex 通過 store 選項,提供了一種機制將狀態從根組件“注入”到每一個子組件中(需調用 Vue.use(Vuex)):
Vue.use(Vuex)
const app = new Vue({
el: '#app',
// 把 store 對象提供給 “store” 選項,這可以把 store 的實例注入所有的子組件
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})
通過在根實例中注冊 store 選項,該 store 實例會注入到根組件下的所有子組件中,且子組件能通過 this.$store 訪問到。讓我們更新下 Counter 的實現:
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
mapState 輔助函數
當一個組件需要獲取多個狀態時候,將這些狀態都聲明為計算屬性會有些重復和冗余。為了解決這個問題,我們可以使用 mapState 輔助函數幫助我們生成計算屬性,讓你少按幾次鍵:
// 在單獨構建的版本中輔助函數為 Vuex.mapState
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
// 箭頭函數可使代碼更簡練
count: state => state.count,
// 傳字符串參數 'count' 等同于 `state => state.count`
countAlias: 'count',
// 為了能夠使用 `this` 獲取局部狀態,必須使用常規函數
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
當映射的計算屬性的名稱與 state 的子節點名稱相同時,我們也可以給 mapState 傳一個字符串數組。
computed: mapState([
// 映射 this.count 為 store.state.count
'count'
])
對象展開運算符
mapState
函數返回的是一個對象。我們如何將它與局部計算屬性混合使用呢?通常,我們需要使用一個工具函數將多個對象合并為一個,以使我們可以將最終對象傳給computed
屬性。但是自從有了對象展開運算符(現處于 ECMASCript 提案 stage-3 階段),我們可以極大地簡化寫法:
computed: {
// 使用對象展開運算符將此對象混入到外部對象中
...mapState({
'count'
})
}
組件仍然保有局部狀態
使用 Vuex 并不意味著你需要將所有的狀態放入 Vuex。雖然將所有的狀態放到 Vuex 會使狀態變化更顯式和易調試,但也會使代碼變得冗長和不直觀。如果有些狀態嚴格屬于單個組件,最好還是作為組件的局部狀態。你應該根據你的應用開發需要進行權衡和確定。
Getter
有時候我們需要從 store 中的 state 中派生出一些狀態,例如對列表進行過濾并計數:
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
如果有多個組件需要用到此屬性,我們要么復制這個函數,或者抽取到一個共享函數然后在多處導入它——無論哪種方式都不是很理想。
Vuex 允許我們在 store 中定義“getter”(可以認為是 store 的計算屬性)。就像計算屬性一樣,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 對象:
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
Getter 也可以接受其他 getter 作為第二個參數:
getters: {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
store.getters.doneTodosCount // -> 1
你也可以通過讓 getter 返回一個函數,來實現給 getter 傳參。在你對 store 里的數組進行查詢時非常有用。
getters: {
// ...
getTodoById: (state) => (id) => {
// 過濾器 返回id === 2的數據
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
mapGetters 輔助函數
mapGetters 輔助函數僅僅是將 store 中的 getter 映射到局部計算屬性:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用對象展開運算符將 getter 混入 computed 對象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
如果你想將一個 getter 屬性另取一個名字,使用對象形式:
mapGetters({
// 映射 `this.doneCount` 為 `store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
Mutation同步事務
更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。
提交載荷(Payload)
你可以向 store.commit 傳入額外的參數,即 mutation 的 載荷
mutations: {
increment (state, n) {
state.count += n
}
}
store.commit('increment', 10)
在大多數情況下,載荷應該是一個對象,這樣可以包含多個字段并且記錄的 mutation 會更易讀:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
可以這樣創建
//創建
export default new Vuex.Store({
mutations
})
//接收
...mapMutations([
'set_audio_data', // 設置audio數據
'set_playMode', // 設置播放模式
'set_playList' // 設置播放列表數據
]),
如果這樣創建Vuex, 可以用
this.set_playMode(val) 調用
Mutation 需遵守 Vue 的響應規則
既然 Vuex 的 store 中的狀態是響應式的,那么當我們變更狀態時,監視狀態的 Vue 組件也會自動更新。這也意味著 Vuex 中的 mutation 也需要與使用 Vue 一樣遵守一些注意事項:
最好提前在你的 store 中初始化好所有所需屬性。
當需要在對象上添加新屬性時,你應該
使用
Vue.set(obj, 'newProp', 123)
, 或者-
以新對象替換老對象。例如,利用 stage-3 的對象展開運算符我們可以這樣寫:
如:let x1 = { a: 1, b: 2 }; x1 = { ...x1, c: 3 }; 現在 x1 為 { a: 1, b: 2, c: 3 }; state.obj = { ...state.obj, newProp: 123 }
Mutation 必須是同步函數
不能使用回調函數
在組件中提交 Mutation
你可以在組件中使用 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)`
]),
this.incrementBy(); 調用
...mapMutations({
add: 'increment' // 將 `this.add()` 映射為 `this.$store.commit('increment')`
})
}
}
Action異步操作
Action 類似于 mutation,不同在于:
- Action 提交的是 mutation
變化
,而不是直接變更狀態。 - Action 可以包含任意異步操作。
讓我們來注冊一個簡單的 action:用到了 ES2015 的參數解構 來簡化代碼
actions: {
increment ({ commit }) {
commit('increment') //commit提交mutation
}
}
分發 Action
Action 通過 store.dispatch 方法觸發:
store.dispatch('increment')
Actions 支持同樣的載荷方式和對象方式進行分發:
// 以載荷形式分發
store.dispatch('incrementAsync', {
amount: 10
})
// 以對象形式分發
store.dispatch({
type: 'incrementAsync',
amount: 10
})
async和await用法
- 只有在async方法里面才能使用await操作符;
- await操作符是針對Task對象的;
- 當方法A調用方法B,方法B方法體內又通過await調用方法C時,如果方法C內部有異步操作,則方法B會等待異步操作執行完,才往下執行;但方法A可以繼續往下執行,不用再等待B方法執行完。
在組件中分發 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())
}
}
一個 store.dispatch 在不同模塊中可以觸發多個 action 函數。在這種情況下,只有當所有觸發函數完成后,返回的 Promise 才會執行。