1.認識Vuex
1.1Vuex是做什么的
官方解釋:Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用 集中式存儲管理 應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。Vuex 也集成到 Vue 的官方調試工具 devtools extension,提供了諸如零配置的 time-travel 調試、狀態快照導入導出等高級調試功能。
狀態管理到底是什么?
狀態管理模式、集中式存儲管理這些名詞聽起來就非常高大上,讓人捉摸不透。其實,你可以簡單的將其看成把需要多個組件共享的變量全部存儲在一個對象里面。然后,將這個對象放在頂層的Vue實例中,讓其他組件可以使用。那么,多個組件是不是就可以共享這個對象中的所有變量屬性了呢?等等,如果是這樣的話,為什么官方還要專門出一個插件Vuex呢?難道我們不能自己封裝一個對象來管理嗎?當然可以,只是我們要先想想VueJS帶給我們最大的便利是什么呢?沒錯,就是響應式。如果你自己封裝實現一個對象能不能保證它里面所有的屬性做到響應式呢?當然也可以,只是自己封裝可能稍微麻煩一些。不用懷疑,Vuex就是為了提供這樣一個在多個組件間共享狀態的插件,用它就可以了。
1.2管理什么狀態呢?
但是,有什么狀態時需要我們在多個組件間共享的呢?如果你做過大型開放,你一定遇到過多個狀態,在多個界面間的共享問題。比如用戶的登錄狀態、用戶名稱、頭像、地理位置信息等等。比如商品的收藏、購物車中的物品等等。這些狀態信息,我們都可以放在統一的地方,對它進行保存和管理,而且它們還是響應式的(待會兒我們就可以看到代碼了,莫着急)。OK,從理論上理解了狀態管理之后,讓我們從實際的代碼再來看看狀態管理。畢竟,Talk is cheap, Show me the code
1.3單界面的狀態管理
我們知道,要在單個組件中進行狀態管理是一件非常簡單的事情,什么意思呢?我們來看下面的圖片。
這圖片中的三種東西,怎么理解呢?
- State:不用多說,就是我們的狀態。(你姑且可以當做就是data中的屬性)
- View:視圖層,可以針對State的變化,顯示不同的信息。(這個好理解吧?)
- Actions:這里的Actions主要是用戶的各種操作:點擊、輸入等等,會導致狀態的改變。
寫點代碼,加深理解:
<template>
<div id="app">
<div>當前計數:{{counter}}</div>
<button @click="counter+=1">+1</button>
<button @click="counter-=1">-1</button>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
counter: 0
}
}
}
</script>
<style>
</style>
在這個案例中,我們有木有狀態需要管理呢?沒錯,就是個數counter。counter需要某種方式被記錄下來,也就是我們的State。counter目前的值需要被顯示在界面中,也就是我們的View部分。界面發生某些操作時(我們這里是用戶的點擊,也可以是用戶的input),需要去更新狀態,也就是我們的Actions,這不就是上面的流程圖了嗎?
1.4多界面狀態管理
Vue已經幫我們做好了單個界面的狀態管理,但是如果是多個界面呢?多個試圖都依賴同一個狀態(一個狀態改了,多個界面需要進行更新),不同界面的Actions都想修改同一個狀態(Home.vue需要修改,Profile.vue也需要修改這個狀態)。
也就是說對於某些狀態(狀態1/狀態2/狀態3)來說只屬於我們某一個試圖,但是也有一些狀態(狀態a/狀態b/狀態c)屬於多個試圖共同想要維護的。狀態1/狀態2/狀態3你放在自己的房間中,你自己管理自己用,沒問題。但是狀態a/狀態b/狀態c我們希望交給一個大管家來統一幫助我們管理!!!沒錯,Vuex就是為我們提供這個大管家的工具。
全局單例模式(大管家)
我們現在要做的就是將共享的狀態抽取出來,交給我們的大管家,統一進行管理。之后,你們每個試圖,按照我規定好的規定,進行訪問和修改等操作。這就是Vuex背后的基本思想。
1.5Vuex狀態管理圖例
2.Vuex基本使用
我們現在來用Vuex實現一下上面的計數器案例
第一步,我們在store中的index.js加上我們的共享變量count

第二步,我們新增一個組件,用來顯示我們的count

第三步,在App.vue中引入組件並對count進行加減操作

最后我們看到頁面結果,當我們點擊+1或者-1按鈕時,按鈕上下的兩個count都會同時變化
3.Vuex核心概念
剛才我們已經了解了Vuex是什么,並且也簡單的使用了Vuex,下面我們來深入探究一下Vuex到底是個啥
Vuex中有幾個比較核心的概念,我們來一一介紹
- State
- Getters
- Mutation
- Action
- Module
3.1State單一狀態樹
Vuex提出使用單一狀態樹, 什么是單一狀態樹呢?英文名稱是Single Source of Truth,也可以翻譯成單一數據源。但是,它是什么呢?我們來看一個生活中的例子。OK,我用一個生活中的例子做一個簡單的類比。
我們知道,在國內我們有很多的信息需要被記錄,比如上學時的個人檔案,工作后的社保記錄,公積金記錄,結婚后的婚姻信息,以及其他相關的戶口、醫療、文憑、房產記錄等等(還有很多信息)。這些信息被分散在很多地方進行管理,有一天你需要辦某個業務時(比如入戶某個城市),你會發現你需要到各個對應的工作地點去打印、蓋章各種資料信息,最后到一個地方提交證明你的信息無誤。這種保存信息的方案,不僅僅低效,而且不方便管理,以及日后的維護也是一個龐大的工作(需要大量的各個部門的人力來維護,當然國家目前已經在完善我們的這個系統了)。
這個和我們在應用開發中比較類似:
如果你的狀態信息是保存到多個Store對象中的,那么之后的管理和維護等等都會變得特別困難。所以Vuex也使用了單一狀態樹來管理應用層級的全部狀態。單一狀態樹能夠讓我們最直接的方式找到某個狀態的片段,而且在之后的維護和調試過程中,也可以非常方便的管理和維護。
3.2Getters
項目中有這樣一個需求,現在有一些學生,我們需要找出其中年齡大於20的學生,這時候我們就可以使用getters來完成,具體實現如下:
index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 100,
//定義學生數組對象
students: [
{id: 1, name: 'kobe', age: 18},
{id: 2, name: 'lucy', age: 21},
{id: 3, name: 'lilei', age: 30},
{id: 4, name: 'jack', age: 12}
]
},
getters: {
//getters中寫你的需求
more20stu(state){
return state.students.filter(s => s.age > 20) //過濾出年齡大於20的學生
}
}
})
App.vue
<template>
<div id="app">
<div>當前計數:{{$store.state.count}}</div>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<!-- 在這里獲取getters方法的返回值 -->
<p>{{$store.getters.more20stu}}</p>
<h3>---------------------我是分隔符---------------------</h3>
<Count1></Count1>
</div>
</template>
<script>
import Count1 from './views/count1.vue'
export default {
name: 'App',
components:{
Count1
},
methods: {
increment() {
this.$store.state.count=this.$store.state.count+1
},
decrement() {
this.$store.state.count-=1
}
}
}
</script>
count1.vue
<template>
<div>
<p>我是對比count{{$store.state.count}}</p>
<p>{{$store.getters.more20stu}}</p>
</div>
</template>
<script>
export default{
name: 'Count1'
}
</script>
<style>
</style>
3.3Mutation
1.Mutation狀態更新
Vuex的store狀態的更新唯一方式:提交Mutation,Mutation主要包括兩部分:
- 字符串的事件類型(type)
- 一個回調函數(handler),該回調函數的第一個參數就是state。
mutation的定義方式:
index.js
mutations: {
increment(state) {
state.count++;
}
}
通過mutation更新
App.vue
increment() {
this.$store.commit('increment')
},
2.Mutation傳遞參數
在通過mutation更新數據的時候, 有可能我們希望攜帶一些額外的參數,參數被稱為是mutation的載荷(Payload)
Mutation中的代碼:
index.js
decrement(state,n) { //n是傳遞過來的參數
state.count -= n;
}
App.vue
//調用mutation 中的decrement方法並傳入參數
decrement() {
this.$store.commit('decrement',5)
}
3.Mutation提交風格
上面的通過commit進行提交是一種普通的方式,Vue還提供了另外一種風格, 它是一個包含type屬性的對象
我們提交的時候可以傳遞一個對象過去,像下面這樣
App.vue
changeCount() {
this.$store.commit({
type: 'changeCount',
count: 100
})
}
index.js
changeCount(state,payload) {
state.count = payload.count
}
4.Mutation響應規則
Vuex的store中的state是響應式的,當state中的數據發生改變時,,Vue組件會自動更新。這就要求我們必須遵守一些Vuex對應的規則:
提前在store中初始化好所需的屬性。當給state中的對象添加新屬性時, 使用下面的方式:
- 方式一: 使用Vue.set(obj, 'newProp', 123)
- 方式二: 用心對象給舊對象重新賦值
我們來看一個例子:
當我們點擊更新信息,界面並沒有發生對應改變

如何才能讓它改變呢?查看下面代碼的方式一和方式二,都可以讓state中的屬性是響應式的

5.Mutation常量類型
我們來考慮下面的問題:
在mutation中, 我們定義了很多事件類型(也就是其中的方法名稱)。當我們的項目增大時, Vuex管理的狀態越來越多, 需要更新狀態的情況越來越多, 那么意味着Mutation中的方法越來越多。方法過多, 使用者需要花費大量的經歷去記住這些方法, 甚至是多個文件間來回切換, 查看方法名稱, 甚至如果不是復制的時候, 可能還會出現寫錯的情況。如何避免上述的問題呢?
在各種Flux實現中, 一種很常見的方案就是使用常量替代Mutation事件的類型。我們可以將這些常量放在一個單獨的文件中, 方便管理以及讓整個app所有的事件類型一目了然。具體怎么做呢?我們可以創建一個文件: mutation-types.js, 並且在其中定義我們的常量。定義常量時, 我們可以使用ES2015中的風格, 使用一個常量來作為函數的名稱。
具體實現如下:

6.Mutation同步函數
通常情況下, Vuex要求我們Mutation中的方法必須是同步方法。主要的原因是當我們使用devtools時, 可以devtools可以幫助我們捕捉mutation的快照。但是如果是異步操作, 那么devtools將不能很好的追蹤這個操作什么時候會被完成。比如我們之前的代碼, 當執行更新時, devtools中會有如下信息: 圖1,但是, 如果Vuex中的代碼, 我們使用了異步函數: 圖2


3.4Action
1.Action的基本定義
我們強調, 不要再Mutation中進行異步操作。但是某些情況, 我們確實希望在Vuex中進行一些異步操作, 比如網絡請求, 必然是異步的。 這個時候怎么處理呢?Action類似於Mutation, 但是是用來代替Mutation進行異步操作的。
Action的基本使用代碼如下:
context是什么?context是和store對象具有相同方法和屬性的對象。也就是說, 我們可以通過context去進行commit相關的操作, 也可以獲取context。state等。但是注意, 這里它們並不是同一個對象, 為什么呢? 我們后面學習Modules的時候, 再具體說。這樣的代碼是否多此一舉呢?我們定義了actions, 然后又在actions中去進行commit, 這不是脫褲放屁嗎?事實上並不是這樣, 如果在Vuex中有異步操作, 那么我們就可以在actions中完成了。
2.Action的分發
在Vue組件中, 如果我們調用action中的方法, 那么就需要使用dispatch
3.Action返回的Promise
前面我們學習ES6語法的時候說過, Promise經常用於異步操作。在Action中, 我們可以將異步操作放在一個Promise中, 並且在成功或者失敗后, 調用對應的resolve或reject。OK, 我們來看下面的代碼:
3.5Module
1.認識Module
Module是模塊的意思,為什么在Vuex中我們要使用模塊呢?
Vue使用單一狀態樹,那么也意味着很多狀態都會交給Vuex來管理。當應用變得非常復雜時,store對象就有可能變得相當臃腫。為了解決這個問題, Vuex允許我們將store分割成模塊(Module),而每個模塊擁有自己的state、mutation、action、getters等。我們按照什么樣的方式來組織模塊呢?我們來看下邊的代碼
2.Module局部狀態
上面的代碼中, 我們已經有了整體的組織結構, 下面我們來看看具體的局部模塊中的代碼如何書寫。我們在moduleA中添加state、mutations、getters,mutation和getters接收的第一個參數是局部狀態對象

3.Actions的寫法
actions的寫法呢? 接收一個context參數對象。局部狀態通過 context.state 暴露出來,根節點狀態則為 context.rootState
如果getters中也需要使用全局的狀態, 可以接受更多的參數