目錄
Vuex 是什么
什么是“狀態管理模式”?
什么情況下我應該使用 Vuex?
使用方法:
提交載荷(Payload)
對象風格的提交方式
使用常量替代 Mutation 事件類型
Mutation 必須是同步函數
在組件中提交 Mutation
下一步:Action
Vuex 是什么
Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式 + 庫。它采用集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化。
什么是“狀態管理模式”?
讓我們從一個簡單的 Vue 計數應用開始:
const Counter = {// 狀態data () {return {count: 0}},// 視圖template: `<div>{{ count }}</div>`,// 操作methods: {increment () {this.count++}}
}createApp(Counter).mount('#app')
這個狀態自管理應用包含以下幾個部分:
?
- 狀態,驅動應用的數據源;
- 視圖,以聲明方式將狀態映射到視圖;
- 操作,響應在視圖上的用戶輸入導致的狀態變化。
以下是一個表示“單向數據流”理念的簡單示意:
但是,當我們的應用遇到多個組件共享狀態時,單向數據流的簡潔性很容易被破壞:
- 多個視圖依賴于同一狀態。
- 來自不同視圖的行為需要變更同一狀態。
對于問題一,傳參的方法對于多層嵌套的組件將會非常繁瑣,并且對于兄弟組件間的狀態傳遞無能為力。對于問題二,我們經常會采用父子組件直接引用或者通過事件來變更和同步狀態的多份拷貝。以上的這些模式非常脆弱,通常會導致無法維護的代碼。
因此,我們為什么不把組件的共享狀態抽取出來,以一個全局單例模式管理呢?在這種模式下,我們的組件樹構成了一個巨大的“視圖”,不管在樹的哪個位置,任何組件都能獲取狀態或者觸發行為!
通過定義和隔離狀態管理中的各種概念并通過強制規則維持視圖和狀態間的獨立性,我們的代碼將會變得更結構化且易維護。
這就是 Vuex 背后的基本思想,借鑒了?Flux、Redux?和?The Elm Architecture。與其他模式不同的是,Vuex 是專門為 Vue.js 設計的狀態管理庫,以利用 Vue.js 的細粒度數據響應機制來進行高效的狀態更新。
如果你想交互式地學習 Vuex,可以看這個?Scrimba 上的 Vuex 課程,它將錄屏和代碼試驗場混合在了一起,你可以隨時暫停并嘗試。
什么情況下我應該使用 Vuex?
Vuex 可以幫助我們管理共享狀態,并附帶了更多的概念和框架。這需要對短期和長期效益進行權衡。
如果您不打算開發大型單頁應用,使用 Vuex 可能是繁瑣冗余的。確實是如此——如果您的應用夠簡單,您最好不要使用 Vuex。一個簡單的?store 模式就足夠您所需了。但是,如果您需要構建一個中大型單頁應用,您很可能會考慮如何更好地在組件外部管理狀態,Vuex 將會成為自然而然的選擇。引用 Redux 的作者 Dan Abramov 的話說就是:
Flux 架構就像眼鏡:您自會知道什么時候需要它。
使用方法:
//vuex文件
import { createStore } from 'vuex'export default createStore({state: {fullscreenLoading: false // 全屏加載},getters: {},mutations: {updateFullLoading(state, status){state.fullscreenLoading = status}},actions: {},modules: {}
})
// 組件
import { useStore } from 'vuex'
export default {setup() {// vuexconst store = useStore()// mutaions修改store.commit('updateFullLoading', true)}
}
更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。Vuex 中的 mutation 非常類似于事件:每個 mutation 都有一個字符串的事件類型 (type)和一個回調函數 (handler)。這個回調函數就是我們實際進行狀態更改的地方,并且它會接受 state 作為第一個參數:
const store = createStore({state: {count: 1},mutations: {increment (state) {// 變更狀態state.count++}}
})
你不能直接調用一個 mutation 處理函數。這個選項更像是事件注冊:“當觸發一個類型為?increment
?的 mutation 時,調用此函數。”要喚醒一個 mutation 處理函數,你需要以相應的 type 調用?store.commit?方法:
store.commit('increment')
提交載荷(Payload)
你可以向?store.commit
?傳入額外的參數,即 mutation 的載荷(payload):
// ...
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
})
對象風格的提交方式
提交 mutation 的另一種方式是直接使用包含?type
?屬性的對象:
store.commit({type: 'increment',amount: 10
})
當使用對象風格的提交方式,整個對象都作為載荷傳給 mutation 函數,因此處理函數保持不變:
mutations: {increment (state, payload) {state.count += payload.amount}
}
使用常量替代 Mutation 事件類型
使用常量替代 mutation 事件類型在各種 Flux 實現中是很常見的模式。這樣可以使 linter 之類的工具發揮作用,同時把這些常量放在單獨的文件中可以讓你的代碼合作者對整個 app 包含的 mutation 一目了然:
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import { createStore } from 'vuex'
import { SOME_MUTATION } from './mutation-types'const store = createStore({state: { ... },mutations: {// 我們可以使用 ES2015 風格的計算屬性命名功能// 來使用一個常量作為函數名[SOME_MUTATION] (state) {// 修改 state}}
})
用不用常量取決于你——在需要多人協作的大型項目中,這會很有幫助。但如果你不喜歡,你完全可以不這樣做。
Mutation 必須是同步函數
一條重要的原則就是要記住?mutation 必須是同步函數。為什么?請參考下面的例子
mutations: {someMutation (state) {api.callAsyncMethod(() => {state.count++})}
}
現在想象,我們正在 debug 一個 app 并且觀察 devtool 中的 mutation 日志。每一條 mutation 被記錄,devtools 都需要捕捉到前一狀態和后一狀態的快照。然而,在上面的例子中 mutation 中的異步函數中的回調讓這不可能完成:因為當 mutation 觸發的時候,回調函數還沒有被調用,devtools 不知道什么時候回調函數實際上被調用——實質上任何在回調函數中進行的狀態的改變都是不可追蹤的。
在組件中提交 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)`]),...mapMutations({add: 'increment' // 將 `this.add()` 映射為 `this.$store.commit('increment')`})}
}
下一步:Action
在 mutation 中混合異步調用會導致你的程序很難調試。例如,當你調用了兩個包含異步回調的 mutation 來改變狀態,你怎么知道什么時候回調和哪個先回調呢?這就是為什么我們要區分這兩個概念。在 Vuex 中,mutation 都是同步事務:
store.commit('increment')
// 任何由 "increment" 導致的狀態變更都應該在此刻完成。
?