Vuex新手入門指南

很多人在學習完Vue.js之後還會看到一個經常被提及的詞語叫做Vuex。

Vuex字面上看與Vue.js只差了一個字母,但是他們兩個做的事情完全不一樣。

在本文我會像之前的Vue.js新手入門指南 - 知乎專欄文章一樣的問答形式來寫文章。

1.Vuex是什麼?

我們還是像以往一樣先看一看官方文檔對此的解讀(Vuex 是什麼? · GitBook)

Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。Vuex 也集成到 Vue 的官方調試工具 devtools extension,提供了諸如零配置的 time-travel 調試、狀態快照導入導出等高級調試功能。

是不是又遇到了很多看起來很高大上聽起來卻一臉懵逼的專業術語?別急別急,我們慢慢來剖析一下這個Vuex究竟是個啥東西,他能做些啥。

2.Vuex到底用來做什麼?

用通俗一點的話來說,Vuex就是一個用於管理SPA項目(不知道SPA是什麼?請參考本專欄代碼之美 - 知乎專欄中的歷史文章)中狀態的開源產品。

接下來又引出了一個問題,什麼是狀態,為什麼要用Vuex這個東西去管理它?

3.什麼是狀態?為什麼要去管理它?

狀態這個東西其實我們生活中隨處可見。我們頭頂上的燈就有兩種狀態,一種是開,一種是關。狀態說白了就是燈這個對象的的某個屬性的值。

如果你對狀態和屬性這兩個概念還是有所不了解,那麼我打一個其他的比方吧。

我們平時是否有玩過王者榮耀或者英雄聯盟LOL之類的網遊?這些遊戲裡面每一個英雄當前都有生命值,法力值,攻擊力,法術強度,護甲和魔抗等等,這些是這個英雄的屬性,也就是英雄這個對象當前的狀態。

屬性分為固定屬性和可變屬性,一般像LOL裡面大部分ADC英雄如果沒有特殊的被動或者其他裝備的支持,那麼它的的攻擊距離都是固定的,這個就是固定屬性,這種固定屬性的狀態由於正常情況下都是不變的,所以我們可以直接寫死在代碼中(這種寫死在代碼中的變數的值稱為硬編碼),但是像其他的攻擊力法術強度等等都是隨裝備和等級變化,那麼這種屬性是可變屬性。

這些屬性的狀態由於會根據用戶的各種操作(比如說出裝備,打怪升級升級)變化。在傳統的Vue.js的組件化開發中,一般這些狀態都是分散在各個組件中,此時此刻如果兩個英雄互相打起來了,那麼就得分別去不同的組件中取狀態值,然後進行狀態值的修改,最後還要互相讀取對方的狀態值。如果他們本身是父子組件,那麼還可以通過事件觸發或者Prop屬性來傳遞狀態,但是如果是不同的組件,由於由於Vue.js本身組件之間有作用域,它們無法直接相互通信,所以就需要一些東西例如Vuex去集中管理和追蹤它的變化。(如果你現在還是不明白這一大段話,可以好好回顧一下官方文檔中組件 - vue.js非父子組件通信 這個部分的內容)

在遊戲中,這些狀態一般以變數的形式保存在內存中,但是由於用戶玩遊戲的時候並不是直接去使用內存管理工具查看他們在內存裡面的值,而是通過遊戲界面去看這些值,所以還需要像Vue.js這種MVVM框架將狀態同步到視圖中。這就是Vue.js和Vuex之間的關係了。

4.什麼情況下我應該使用 Vuex?

官方文檔(Vuex 是什麼? · GitBook)中說:

雖然 Vuex 可以幫助我們管理共享狀態,但也附帶了更多的概念和框架。這需要對短期和長期效益進行權衡。

如果您不打算開發大型單頁應用,使用 Vuex 可能是繁瑣冗餘的。確實是如此——如果您的應用夠簡單,您最好不要使用 Vuex。一個簡單的 global event bus 就足夠您所需了。但是,如果您需要構建是一個中大型單頁應用,您很可能會考慮如何更好地在組件外部管理狀態,Vuex 將會成為自然而然的選擇。引用 Redux 的作者 Dan Abramov 的話說就是:

Flux 架構就像眼鏡:您自會知道什麼時候需要它。

他的意思其實就是如果開發的程序並不是很龐大,一個頁面中的組件不是很多並且他們之間並不需要大量頻繁的互相讀寫操作,那麼就可以直接使用傳統的Vue.js中的組件Prop或者事件觸發來修改狀態,或者用組件 - vue.js#非父子組件通信 中介紹的new一個空的Vue對象實例,並且通過事件觸發等方式來跨組件通信。

否則的話還是建議使用Vuex。雖然Vuex本身需要有一段時間的學習成本,但是這個學習成本相對於你開發時期使用傳統非父子組件通信機制遇到的各種坑來說還是比較划算的。這個就看你自己的權衡了。

5.Vuex怎麼安裝和使用?

在前面講解Vue.js入門的時候,我們用的是Vue-Cli這個腳手架工具來搭建的,由於這個腳手架工具本身會幫我們配置好npm的package.json文件,這個文件裡面包含了這個Vue.js項目中所有依賴的包。

但是默認情況下這個腳手架工具沒有為我們將Vuex這個依賴包給包含進去,所以我們得自己去「聲明」一下我們這個Vue.js項目中需要依賴Vuex這個包。

我們該怎麼「聲明」呢?現在有兩種辦法:

一種是直接修改package.json,這種方法看起來有點複雜,很多新手怕一不小心修改出錯,可能會導致整個package.json文件結構出錯,影響以後項目的依賴安裝。

還有一種方法比較安全,只需要一行命令:

npm install vuex --save

表示安裝vuex這個包,--save表示將這個依賴包與本項目的依賴關係寫入package.json中。

然後我們僅僅安裝了這個依賴包是沒有用的,我們還得在之前Vue-Cli為我們自動構建好的項目文件中的main.js主入口文件的開頭裡面加上兩行這樣的代碼:

import Vuex from "vuex"Vue.use(Vuex)

第一行是用ECMAScript6的import將vuex包導入進來(這個是不是和java中導入jar包以及php中導入命名空間很相似?)

第二行是Vue.js本身的插件注入語法(參考官方文檔插件 - vue.js),將插件注入Vue.js的目的是方便我們在插件內部調用它。

6.官方文檔的五大核心概念是什麼?

打開官方文檔(Introduction · GitBook)能看到五大核心概念,他們都是啥?看了半天官方文檔我還是對它們沒什麼了解,樓主能不能以通俗易懂的方式講解一下它們的作用?當然可以啦!

首先先看一遍這個代碼,不需要你看懂它。

const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }})

7.State(狀態)

官方文檔:State · GitBook

這個狀態就是前面所介紹的「狀態」值的存放處。

看第6節末尾的代碼中,狀態就是在state屬性中以鍵值對的形式聲明這個SPA中所有的狀態。上面的代碼中聲明了一個count狀態。

之所以要在這裡聲明所有狀態的原因,一是讓代碼更加優雅,如果你接手你同事的項目的時候,能夠一眼從Vuex的狀態聲明中看出這個應用中有哪些狀態,肯定開發效率杠杠滴。二是如果在這裡聲明了狀態,那麼Vuex就能夠追蹤到這個狀態的變化。那麼Vue.js中就可以在視圖中對這個狀態做出響應。

讀取狀態當然也是直接讀取這個屬性裡面的各種子屬性了。

8.Getter(獲取器)

官方文檔:Getters · GitBook

這個獲取器和一些後端開發中模型層ORM中的獲取器其實是差不多的功能。

比如說後端返回給我們的是一個int類型的時間戳,我們想把這個時間戳轉換成正常人類可讀的文本型時間表現形式(比如說2017年3月11日 12:43:31),那麼我們就得在所有獲取該狀態的代碼中增加一個轉換函數。

可是現在有了狀態獲取器之後,我們可以統一將這個時間戳轉字元串的函數寫在獲取器裡面,要調用的時候就直接調用獲取器就好了。

還有一些其他場景也可以使用獲取器,比如說像錯誤碼這種東西一般都是一個數字碼對應一個文字形式的錯誤原因,我們也可以通過獲取器來實現通過錯誤碼拉取錯誤原因的功能。

使用獲取器的方法則是直接調用Vuex實例的getter下的各種函數即可。

9.Mutations(轉變)

官方文檔:Mutations · GitBook

這個Mutations其實國內目前也沒有比較好的翻譯,通常我們都是直接稱Mutations。

我們前面只講了可以通過調用Vuex的實例的state屬性或者getter獲取器來讀取狀態。但是沒講到如何修改狀態。

官方文檔中已經講了需要先在Vuex實例的Mutations下編寫對應的修改函數來修改狀態。並且要修改的時候,要通過Vuex實例的commit方法來提交修改。也就是說任何對state狀態的修改操作都必須寫在Mutations中,並且還得用Vuex實例的commit來提交修改操作,並且由於Mutations函數可以傳入參數,所以commit同理也可以傳入參數。

這個時候可能有一些同學就會提問了,前面既然講到了讀取可以直接訪問Vuex實例的state屬性,為什麼修改卻不能直接去操作Vuex實例的state呢?官方文檔和其他高手的回答是:

再次強調,我們通過提交 mutation 的方式,而非直接改變 store.state.count,是因為我們想要更明確地追蹤到狀態的變化。這個簡單的約定能夠讓你的意圖更加明顯,這樣你在閱讀代碼的時候能更容易地解讀應用內部的狀態改變。此外,這樣也讓我們有機會去實現一些能記錄每次狀態改變,保存狀態快照的調試工具。有了它,我們甚至可以實現如時間穿梭般的調試體驗。

相當於我們通過一個Mutations函數可以顯式的在代碼中告訴開發者,我們這個SPA中究竟會對狀態進行哪些操作,操作方式是什麼。並且在後期我們使用一些輔助開發工具,可以保存狀態的快照,就像git或者svn一樣可以回滾狀態。如果你還是有點不明白,總之你就按照官方文檔說的去做吧,等開發一段時間之後會慢慢明白作者的良苦用心的,哈哈。

還有一個問題就是為什麼狀態修改的提交必須通過Vuex實例的commit方法提交呢?為什麼不能直接調用Mutations函數呢?除了上面官方文檔中提到的原因,網上還有高手解釋了:因為Vue.js的狀態修改其實是在內部有一個修改隊列,通過commit的方式提交修改可以保證狀態的修改是有序的。

10.Actions(動作)

官方文檔:Actions · GitBook

前面提到了Mutations中可以對狀態進行操作,但是忘記告訴各位同學,Mutations中對狀態的操作只能是同步操作,不能是非同步操作。

如果這個時候我們有一個對狀態的修改操作是非同步的怎麼辦呢?

首先看看什麼是非同步操作?比如說ajax就可以選擇是否發起非同步請求,發起非同步請求之後,我們就需要在回調函數裡面進行請求結果的處理。關於JavaScript非同步的知識大家可以先使用各種搜索引擎自學一下。

現在回到actions上來,看看官方文檔(Actions · GitBook):

Action 類似於 mutation,不同在於:

  • Action 提交的是 mutation,而不是直接變更狀態。
  • Action 可以包含任意非同步操作。

其實非同步的狀態修改本質上還是通過幾個同步操作組合的,所以我們還是得先聲明好mutation同步操作方法,然後在actions中進行非同步操作。如果我們暫時手頭上沒有ajax介面用於非同步請求,那麼我們可以像官方文檔一樣用setTimeout這種最簡單的測試方法來理解。

actions: { incrementAsync ({ commit }) { setTimeout(() => { commit("increment") }, 1000) }}

前面講到了mutation是用commit來提交操作的,那麼actions是怎麼提交的呢?官方文檔中說了actions是使用Vuex實例的dispatch方法來提交(其實說分發會更加準確)的。

至於其他更詳細的actions操作官方文檔講的還是比較清楚的,沒有什麼比較複雜的概念,可以參考官方文檔學習,這裡不做更多講解。

至於後面「組合actions」中提到的Promise對象以及 async / await 都是JavaScript中的一些特性,大家可以利用搜索引擎進行更多了解。

11.Modules(模塊)

官方文檔:Modules · GitBook

如果你的SPA項目非常的龐大,那麼狀態可能本身還需要進行分模塊分類管理,這個時候就需要用到模塊了。官方文檔中已經給出了比較詳細的模塊操作代碼,這裡不再做更多講解。

至於前面在將actions的時候,官方文檔中說actions方法在聲明的時候需要帶上一個context參數,原因在這裡可以得到解答:

對於模塊內部的 action,context.state 是局部狀態,根節點的狀態是 context.rootState

12.嚴格模式

官方文檔:嚴格模式 · GitBook

在嚴格模式下,無論何時發生了狀態變更且不是由 mutation 函數引起的,將會拋出錯誤。這能保證所有的狀態變更都能被調試工具跟蹤到。

前面提到了state的修改需要通過提交Mutations或者分發Actions,但是事實上我直接修改state可以嗎?當然也是可以的,但是在開發階段,為了儘可能防止開發者直接修改,就可以通過嚴格模式來檢測這種錯誤的修改方式,並且拋出異常。

但是官方文檔後面也提到了:

不要在發布環境下啟用嚴格模式!嚴格模式會深度監測狀態樹來檢測不合規的狀態變更——請確保在發布環境下關閉嚴格模式,以避免性能損失。

因此不要在生產環境下開啟嚴格模式導致性能損失。

結語:

Vuex綜合來看是一個非常適合在Vue.js中使用的狀態管理工具,當然類似的狀態管理工具也有很多,比如說React的Redux。

但是我們為了能夠儘可能保證項目穩定性以及學習曲線的平滑,推薦在Vue.js中使用Vuex。

其實文章中也還有很多細節部分沒有講到,這些細節官方文檔的說明還是比較通俗易懂這裡就不做更多搬運來湊字數。當然後面也有熱重載,測試等方面由於樓主自己的項目中也未使用過,所以不敢留有更多筆墨,還等樓主繼續探索實踐才能寫出更多好文章。

本文章由 @昌維 原創,在知乎專欄-代碼之美 https://zhuanlan.zhihu.com/codes 首發,轉載請註明出處。大家喜歡和支持我的文章可以點開我的頭像以及專欄名稱進行關注,或是點擊下方的打賞按鈕進行支持,謝謝。^_^

推薦閱讀:

vue組件什麼條件下需要摧毀?

TAG:前端开发 | Vuejs | Vuex |