Vuex詳細教程

說故事的五公子 發表於 2020-08-01
Vue

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單介面的狀態管理

我們知道,要在單個元件中進行狀態管理是一件非常簡單的事情,什麼意思呢?我們來看下面的圖片。

image-20200730134601007

這圖片中的三種東西,怎麼理解呢?

  • 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狀態管理圖例

image-20200730135559737

2.Vuex基本使用

我們現在來用Vuex實現一下上面的計數器案例

第一步,我們在store中的index.js加上我們的共享變數count

image-20200730200641524

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

image-20200730200943071

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

image-20200730201304685

最後我們看到頁面結果,當我們點選+1或者-1按鈕時,按鈕上下的兩個count都會同時變化

image-20200730201403263

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>

image-20200731055305319

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)
  • 方式二: 用心物件給舊物件重新賦值

我們來看一個例子:
當我們點選更新資訊,介面並沒有發生對應改變

image-20200731061809688

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

image-20200731061847381

5.Mutation常量型別

我們來考慮下面的問題:
在mutation中, 我們定義了很多事件型別(也就是其中的方法名稱)。當我們的專案增大時, Vuex管理的狀態越來越多, 需要更新狀態的情況越來越多, 那麼意味著Mutation中的方法越來越多。方法過多, 使用者需要花費大量的經歷去記住這些方法, 甚至是多個檔案間來回切換, 檢視方法名稱, 甚至如果不是複製的時候, 可能還會出現寫錯的情況。如何避免上述的問題呢?

在各種Flux實現中, 一種很常見的方案就是使用常量替代Mutation事件的型別。我們可以將這些常量放在一個單獨的檔案中, 方便管理以及讓整個app所有的事件型別一目瞭然。具體怎麼做呢?我們可以建立一個檔案: mutation-types.js, 並且在其中定義我們的常量。定義常量時, 我們可以使用ES2015中的風格, 使用一個常量來作為函式的名稱。

具體實現如下:

image-20200731062218324

6.Mutation同步函式

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

image-20200731062352080 image-20200731062415100

3.4Action

1.Action的基本定義

我們強調, 不要再Mutation中進行非同步操作。但是某些情況, 我們確實希望在Vuex中進行一些非同步操作, 比如網路請求, 必然是非同步的。 這個時候怎麼處理呢?Action類似於Mutation, 但是是用來代替Mutation進行非同步操作的。
Action的基本使用程式碼如下:

context是什麼?context是和store物件具有相同方法和屬性的物件。也就是說, 我們可以通過context去進行commit相關的操作, 也可以獲取context。state等。但是注意, 這裡它們並不是同一個物件, 為什麼呢? 我們後面學習Modules的時候, 再具體說。這樣的程式碼是否多此一舉呢?我們定義了actions, 然後又在actions中去進行commit, 這不是脫褲放屁嗎?事實上並不是這樣, 如果在Vuex中有非同步操作, 那麼我們就可以在actions中完成了。

image-20200731062634356

2.Action的分發

在Vue元件中, 如果我們呼叫action中的方法, 那麼就需要使用dispatch

image-20200731062822692

3.Action返回的Promise

前面我們學習ES6語法的時候說過, Promise經常用於非同步操作。在Action中, 我們可以將非同步操作放在一個Promise中, 並且在成功或者失敗後, 呼叫對應的resolve或reject。OK, 我們來看下面的程式碼:

image-20200731062926987

3.5Module

1.認識Module

Module是模組的意思,為什麼在Vuex中我們要使用模組呢?

Vue使用單一狀態樹,那麼也意味著很多狀態都會交給Vuex來管理。當應用變得非常複雜時,store物件就有可能變得相當臃腫。為了解決這個問題, Vuex允許我們將store分割成模組(Module),而每個模組擁有自己的state、mutation、action、getters等。我們按照什麼樣的方式來組織模組呢?我們來看下邊的程式碼

image-20200731151512127

2.Module區域性狀態

上面的程式碼中, 我們已經有了整體的組織結構, 下面我們來看看具體的區域性模組中的程式碼如何書寫。我們在moduleA中新增state、mutations、getters,mutation和getters接收的第一個引數是區域性狀態物件

image-20200731151656715

3.Actions的寫法

actions的寫法呢? 接收一個context引數物件。區域性狀態通過 context.state 暴露出來,根節點狀態則為 context.rootState

image-20200731151837586

如果getters中也需要使用全域性的狀態, 可以接受更多的引數

image-20200731151855338