Vuex是什么?
Vuex 是一個專為 Vue.js應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化。Vuex 也集成到Vue 的官方調試工具 devtools extension,提供了諸如零配置的 time-travel調試、狀態快照導入導出等高級調試功能。
反正我是懵懵的,沒太看懂他有什么用。 (~ ̄▽ ̄)~
什么是“狀態管理模式”?
讓我們從一個簡單的 Vue 計數應用開始:
newVue({// statedata () {return{count:0}? ? ? },// viewtemplate: `
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
這個狀態自管理應用包含以下幾個部分:
state,驅動應用的數據源; view,以聲明方式將state映射到視圖; actions,響應在view上的用戶輸入導致的狀態變化。
我的理解是 state狀態就是數據源,通常用data,view是視圖層不用說,通常用template,action就是方法進行一些數據操作,通常用methods。
當我們的應用遇到多個組件共享狀態時,單向數據流的簡潔性很容易被破壞:
-多個視圖依賴于同一狀態。
-來自不同視圖的行為需要變更同一狀態。
應該是 很多視圖層文件都是同一個數據來源,不同視圖的操作需要改為同一個數據源。
對于問題一,傳參的方法對于多層嵌套的組件將會非常繁瑣,并且對于兄弟組件間的狀態傳遞無能為力。對于問題二,我們經常會采用父子組件直接引用或者通過事件來變更和同步狀態的多份拷貝。以上的這些模式非常脆弱,通常會導致無法維護的代碼。
因此,我們為什么不把組件的共享狀態抽取出來,以一個全局單例模式管理呢?在這種模式下,我們的組件樹構成了一個巨大的“視圖”,不管在樹的哪個位置,任何組件都能獲取狀態或者出發行為!
另外,通過定義和隔離狀態管理中的各種概念并強制遵守一定的規則,我們的代碼將會變得更結構化且易維護。
這就是 Vuex 背后的基本思想,借鑒了 Flux、Redux、和 The Elm Architecture。與其他模式不同的是,Vuex
是專門為 Vue.js 設計的狀態管理庫,以利用 Vue.js 的細粒度數據響應機制來進行高效的狀態更新。
也就是遇到上述問題怎么辦呢,Vuex將數據源打包了,然后要操作的時候,都從這個數據源來操作,不會有那種繼承父組件,多層組件嵌套導致不利于維護的情況。
什么情況下我應該使用 Vuex?
雖然 Vuex 可以幫助我們管理共享狀態,但也附帶了更多的概念和框架。這需要對短期和長期效益進行權衡。
如果您不打算開發大型單頁應用,使用 Vuex 可能是繁瑣冗余的。確實是如此——如果您的應用夠簡單,您最好不要使用 Vuex。一個簡單的
global event bus
就足夠您所需了。但是,如果您需要構建是一個中大型單頁應用,您很可能會考慮如何更好地在組件外部管理狀態,Vuex 將會成為自然而然的選擇。引用
Redux 的作者 Dan Abramov 的話說就是:
Flux架構就像眼鏡:您自會知道什么時候需要它。
其實我真的覺得這段話說了等于沒說。。。 ㄟ( ▔, ▔ )ㄏ
每一個 Vuex 應用的核心就是 store(倉庫)。”store” 基本上就是一個容器,它包含著你的應用中大部分的狀態(即state)。Vuex 和單純的全局對象有以下兩點不同:
1.Vuex 的狀態存儲是響應式的。當 Vue 組件從 store 中讀取狀態的時候,若 store中的狀態發生變化,那么相應的組件也會相應地得到高效更新。
2.你不能直接改變 store 中的狀態。改變 store 中的狀態的唯一途徑就是顯式地提交mutations。這樣使得我們可以方便地跟蹤每一個狀態的變化,從而讓我們能夠實現一些工具幫助我們更好地了解我們的應用。
1大概也就是數據綁定,同步更新數據,2不是很懂,mutations是什么東西?
最簡單的 Store
提示:我們將在后續的文檔示例代碼中使用 ES2015 語法。如果你還沒能掌握 ES2015,你得抓緊了!
安裝 Vuex 之后,讓我們來創建一個 store。創建過程直截了當——僅需要提供一個初始 state 對象和一些 mutations:
// 如果在模塊化構建系統中,請確保在開頭調用了 Vue.use(Vuex)const store =newVuex.Store({? state: {count:0},? mutations: {? ? increment (state) {? ? ? state.count++? ? }? }})
1
2
3
4
5
6
7
8
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12
現在,你可以通過 store.state 來獲取狀態對象,以及通過 store.commit 方法觸發狀態變更:
store.commit('increment')console.log(store.state.count)// -> 1
1
2
3
1
2
3
再次強調,我們通過提交 mutation 的方式,而非直接改變
store.state.count,是因為我們想要更明確地追蹤到狀態的變化。這個簡單的約定能夠讓你的意圖更加明顯,這樣你在閱讀代碼的時候能更容易地解讀應用內部的狀態改變。此外,這樣也讓我們有機會去實現一些能記錄每次狀態改變,保存狀態快照的調試工具。有了它,我們甚至可以實現如時間穿梭般的調試體驗。
由于 store 中的狀態是響應式的,在組件中調用 store 中的狀態簡單到僅需要在計算屬性中返回即可。觸發變化也僅僅是在組件的methods 中提交 mutations。
這是一個最基本的 Vuex 記數應用示例。
接下來,我們將會更深入地探討一些核心概念。讓我們先從 State 概念開始。
反正每次對數據對象進行操作的時候,都需要進行commit,好處就是他上面說的,但是現在不在項目中,肯定體會不到好處,就像redux作者說的“您自會知道什么時候需要它。”
單一狀態樹
Vuex 使用 單一狀態樹 ——
是的,用一個對象就包含了全部的應用層級狀態。至此它便作為一個『唯一數據源(SSOT)』而存在。這也意味著,每個應用將僅僅包含一個 store
實例。單一狀態樹讓我們能夠直接地定位任一特定的狀態片段,在調試的過程中也能輕易地取得整個當前應用狀態的快照。
單狀態樹和模塊化并不沖突 —— 在后面的章節里我們會討論如何將狀態和狀態變更事件分布到各個子模塊中。
每個應用將僅僅包含一個 store
在 Vue 組件中獲得 Vuex 狀態
那么我們如何在 Vue 組件中展示狀態呢?由于 Vuex 的狀態存儲是響應式的,從 store
實例中讀取狀態最簡單的方法就是在計算屬性中返回某個狀態:
// 創建一個 Counter 組件const Counter = {? template: `
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
每當 store.state.count 變化的時候, 都會重新求取計算屬性,并且觸發更新相關聯的 DOM。
然而,這種模式導致組件依賴的全局狀態單例。在模塊化的構建系統中,在每個需要使用 state
的組件中需要頻繁地導入,并且在測試組件時需要模擬狀態。
Vuex 通過 store 選項,提供了一種機制將狀態從根組件『注入』到每一個子組件中(需調用 Vue.use(Vuex)):
constapp =newVue({? el:'#app',// 把 store 對象提供給 “store” 選項,這可以把 store 的實例注入所有的子組件store,? components: { Counter },? template: `? ? `})
1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11
通過在根實例中注冊 store 選項,該 store 實例會注入到根組件下的所有子組件中,且子組件能通過 this.$store訪問到。讓我們更新下 Counter 的實現:
const Counter = {? template: `
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
通過一種類似虛擬主機的機制,將store注入到每個子組件,然后子組件就能操作store了,還是有點像繼承。
mapState 輔助函數
當一個組件需要獲取多個狀態時候,將這些狀態都聲明為計算屬性會有些重復和冗余。為了解決這個問題,我們可以使用 mapState
輔助函數幫助我們生成計算屬性
這個我看的不是很懂,只知道他是輔助計算屬性的。
mapState函數返回的是一個對象。我們如何將它與局部計算屬性混合使用呢?通常,我們需要使用一個工具函數將多個對象合并為一個,以使我們可以將最終對象傳給 computed 屬性。但是自從有了對象展開運算符(現處于 ECMASCript 提案 stage-3 階段),我們可以極大地簡化寫法。
上面的沒懂,那這個等等再說。
有時候我們需要從 store 中的 state 中派生出一些狀態,例如對列表進行過濾并計數:
computed:{? doneTodosCount () {? ? return this.$store.state.todos.filter(todo => todo.done).length}}
1
2
3
4
5
1
2
3
4
5
如果有多個組件需要用到此屬性,我們要么復制這個函數,或者抽取到一個共享函數然后在多處導入它 —— 無論哪種方式都不是很理想。
Vuex 允許我們在 store 中定義『getters』(可以認為是 store 的計算屬性)。Getters 接受 state 作為其第一個參數:
conststore =newVuex.Store({? state: {? ? todos: [? ? ? { id:1, text:'...', done:true},? ? ? { id:2, text:'...', done:false}? ? ]? },? getters: {? ? doneTodos: state => {returnstate.todos.filter(todo => todo.done)? ? }? }})
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
Getters 會暴露為 store.getters 對象:
store.getters.doneTodos // -> [{ id:1, text:'...',done:true}]
1
1
Getters 也可以接受其他 getters 作為第二個參數:
getters: {? //...doneTodosCount: (state, getters) => {returngetters.doneTodos.length? }}store.getters.doneTodosCount // ->1
1
2
3
4
5
6
7
1
2
3
4
5
6
7
我們可以很容易地在任何組件中使用它:
computed:{? doneTodosCount () {? ? return this.$store.getters.doneTodosCount}}
1
2
3
4
5
1
2
3
4
5
getters可以認為是 store 的計算屬性,也就是多次調用,所以將其封裝成getters,方便調用。
mapGetters 輔助函數
mapGetters 輔助函數僅僅是將 store 中的 getters 映射到局部計算屬性:
import { mapGetters } from'vuex'export default {? //...computed: {? // 使用對象展開運算符將 getters 混入 computed 對象中? ? ...mapGetters(['doneTodosCount','anotherGetter',? ? ? //...])? }}
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
如果你想將一個 getter 屬性另取一個名字,使用對象形式:
mapGetters({? // 映射 this.doneCount 為 store.getters.doneTodosCountdoneCount:'doneTodosCount'})
1
2
3
4
1
2
3
4
和上面的state一樣,沒太明白。不著急。
更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。Vuex 中的 mutations 非常類似于事件:每個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)。這個回調函數就是我們實際進行狀態更改的地方,并且它會接受 state 作為第一個參數:
const store =newVuex.Store({? state: {count:1},? mutations: {? ? increment (state) {// 變更狀態state.count++? ? }? }})
1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11
你不能直接調用一個 mutation handler。這個選項更像是事件注冊:“當觸發一個類型為 increment 的 mutation 時,調用此函數。”要喚醒一個 mutation handler,你需要以相應的 type 調用 store.commit 方法:
store.commit('increment')
1
1
我到現在還是沒有明白 mutations是干什么的,具體有什么用。只知道要操作state就是要commit mutations。
提交載荷(Payload)
你可以向 store.commit 傳入額外的參數,即 mutation 的 載荷(payload):
//...mutations: {? increment (state, n) {? ? state.count += n? }}store.commit('increment',10)
1
2
3
4
5
6
7
1
2
3
4
5
6
7
在大多數情況下,載荷應該是一個對象,這樣可以包含多個字段并且記錄的 mutation 會更易讀:
//...mutations: {? increment (state, payload) {? ? state.count += payload.amount? }}store.commit('increment', {? amount:10})
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
不知道載荷用的多不多,感覺還挺多的,荷載就是 commit額外的參數。
對象風格的提交方式
提交 mutation 的另一種方式是直接使用包含 type 屬性的對象:
store.commit({type:'increment',? amount:10})
1
2
3
4
1
2
3
4
當使用對象風格的提交方式,整個對象都作為載荷傳給 mutation 函數,因此 handler 保持不變:
mutations:{? increment (state, payload) {? ? state.count+= payload.amount}}
1
2
3
4
5
1
2
3
4
5
感覺這種風格更適用于vue,更像vue的寫法
Mutations 需遵守 Vue 的響應規則
既然 Vuex 的 store 中的狀態是響應式的,那么當我們變更狀態時,監視狀態的 Vue 組件也會自動更新。這也意味著 Vuex 中的 mutation 也需要與使用 Vue 一樣遵守一些注意事項:
最好提前在你的 store 中初始化好所有所需屬性。
當需要在對象上添加新屬性時,你應該使用Vue.set(obj, 'newProp', 123),或者 -
以新對象替換老對象。例如,利用 stage-3 的對象展開運算符我們可以這樣寫:
state.obj= { ...state.obj, newProp:123}
1
1
規則很重要,我愣是擠出了這幾個沒用的字。 Σ( ° △ °|||)︴
使用常量替代 Mutation 事件類型
使用常量替代 mutation 事件類型在各種 Flux 實現中是很常見的模式。這樣可以使 linter 之類的工具發揮作用,同時把這些常量放在單獨的文件中可以讓你的代碼合作者對整個 app 包含的 mutation 一目了然:
// mutation-types.jsexport const SOME_MUTATION ='SOME_MUTATION'// store.jsimport Vuex from'vuex'import { SOME_MUTATION } from'./mutation-types'const store = new Vuex.Store({? state: {...},? mutations: {? ? // 我們可以使用 ES2015 風格的計算屬性命名功能來使用一個常量作為函數名? ? [SOME_MUTATION] (state) {? ? ? // mutate state? ? }? }})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
用不用常量取決于你 —— 在需要多人協作的大型項目中,這會很有幫助。但如果你不喜歡,你完全可以不這樣做。
什么可不可以這樣做,這個到底是干嘛的啊 ヾ(?`Д′?),難道是方便改事件名?
mutation 必須是同步函數
一條重要的原則就是要記住 mutation 必須是同步函數。為什么?請參考下面的例子:
mutations:{? someMutation (state) {? ? api.callAsyncMethod(() => {? ? ? state.count++? ? })? }}
1
2
3
4
5
6
7
1
2
3
4
5
6
7
現在想象,我們正在 debug 一個 app 并且觀察 devtool 中的 mutation 日志。每一條 mutation 被記錄,devtools 都需要捕捉到前一狀態和后一狀態的快照。然而,在上面的例子中 mutation 中的異步函數中的回調讓這不可能完成:因為當 mutation 觸發的時候,回調函數還沒有被調用,devtools 不知道什么時候回調函數實際上被調用 —— 實質上任何在回調函數中進行的的狀態的改變都是不可追蹤的。
這個如果不是同步函數的話,就顯示不了回調函數的狀態日志了,可以這樣理解嗎?
在組件中提交 Mutations
你可以在組件中使用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({? ? ? add:'increment'// 映射 this.add() 為 this.$store.commit('increment')? ? })? }}
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
這個倒是能看懂
下一步:Actions
在 mutation 中混合異步調用會導致你的程序很難調試。例如,當你能調用了兩個包含異步回調的 mutation 來改變狀態,你怎么知道什么時候回調和哪個先回調呢?這就是為什么我們要區分這兩個概念。在 Vuex 中,mutation 都是同步事務:
store.commit('increment')// 任何由"increment"導致的狀態變更都應該在此刻完成。