# 5-1 Vuex 簡介

在本書第二章談元件之間的資料傳遞時,我們稍微提到了 Vuex。 Vuex 與 Vue Router 一樣作為 Vue.js 框架生態圈的一部分,在這個章節, 我們就來深入理解 Vuex 是什麼、為什麼要使用它,以及它所帶來的好處。

# 為什麼需要 Vuex

快速複習一下,在第二章我們曾經講過,在 Vue.js 裡面,最小的功能單位就是元件了,而每個元件都有自己的狀態 (data) 、邏輯以及模板樣式等。

隨著網站應用的成長、元件的增加,狀態管理變得越來越重要。 既然我們要拆分元件,就是希望它能夠達到最大的可重用性 (Reuseability)。

所以說,元件內的狀態應該盡量保持 「高內聚,低耦合」 (high cohesion, low coupling) 的概念來進行開發。 這樣做的用意是希望讓元件可以獨立的發展,而無須過度依賴外部的功能。如此一來,元件本身可以在不影響其他程式的情況下自由的修改與變化,而外部程式或其他元件的任何修改也不會影響到這個元件本身的功能與運作。

在 Vue.js 裡面,元件之間的狀態更新,不會由某個元件「直接」去修改另一個元件的狀態,而是透過 Props 來接收外部資料,透過 Event 的方式去通知另一個元件來更新,也就是我們常說的「Props in, Events out」:

元件間的資料流向

雖然透過這樣的方式,可以滿足父子元件間的狀態傳遞與更新,但實際在開發應用時,我們不可能整個網站只有父與子兩個元件。

以一個電商的購物車來說,不管是從首頁、商品列表、商品說明,甚至是結帳流程,我們都有可能會更動到「購物車」元件的內容,像是數量的增減、物品的加入移除等等,這就表示可能會有多個元件的狀態都會與這個「購物車」元件的狀態有著緊密地連動關係。

以元件樹結構來說,若我們只是希望幫購物車裡的某個商品數量加個一,這個時候若要從最底層一路一路傳遞到根元件 (頂層),再逐一往下傳遞到那些需要同步狀態的元件,這時顯然這個專案裡的所有元件就有著極高的耦合,因為只要抽掉中間任何一層,你的狀態就無法同步了。 你以為你拆出了各種元件,但是這個無法被拆分再重新組合的應用,事實上就只是一個超大的元件,因為你的單元無法被拆到其他地方重複使用了。

而本章節所要介紹的 Vuex 就是為了解決這個問題所開發的。

# Vuex 的特性

我們都知道 JavaScript 有 Scoped 的概念,不管是 var 的 function 或是 ES6 之後的 letconst 的 block。 過去像是 jQuery 時期,我們要處理跨模組/程式 scope 的資料時,最簡單的方式就是一言不合就往 window 全域物件丟。

好處是幾乎零成本方便地達到我們想要的結果,但壞處也是這個 window 全域物件太方便到無論是誰都能修改, 無論是自己不小心犯的錯,或是變數命名太菜市場跟別人衝突,總之在無形之中也增加了除錯的困難性。

Vuex 提供的 Store (倉庫) 本質上也類似這個 window 全域物件,任何元件都能去存取這個共用的倉庫, 只是它是一個受限制的物件,存取時必須遵守它的規定,也因為這樣我們才能夠避開在過去動不動就修改全域物件的災難。

而 Vuex 除了提供了共用的倉庫之外,由於 Vue.js 資料的響應式更新特性,所以只要引入了 Vuex 的元件,都能在 Store 裡的狀態被改變的時候,同步更新到所有引用的元件裡,同時也可以利用 module (類似 namesapce) 來將儲存的狀態、方法等進行分類。

# Single Source Of Truth (單一資訊來源)

共用狀態的管理

作為共用資料的管理,我們應該將 Vuex 的 store 視為單一資訊來源 (Single Source Of Truth, SSOT) 。 換句話說,當我們在管理相同的資料時,這份資料應該統一由一個源頭進行管理,觀念類似我們曾在本書 1-3 小節談 computed 計算屬性的「幣值轉換器」範例一樣。

這樣的好處是我們無需像過去一樣,在每一個元件的 data 重複定義同一份資料結構,而且,當資料的來源能確保唯一性時, 代表我們在除錯時需要檢查的檢查點也相對變少,引用這份狀態的眾多元件們才能達到狀態完全統一的結果。

# 單向資料流

單向資料流

除了單一資料來源外,同時我們也要確保狀態的流動是「單一性」的,這與我們在【2-2 元件之間的溝通傳遞】小節所談論的概念類似, 當我們無法確保資料更新的來源為何,此時也就無法追蹤這份資料是被誰修改,就像過去在 window 這個全域物件一樣雜亂不受控制。

另外,由於資料流是單向的,所以 Vue.js 的響應式更新才能落實到每個元件上,當資料被更新,Vue / Vuex 馬上就會知道, 不需要透過迴圈一個一個去檢查哪個元件的狀態是否已經更新完成。

# 導入與安裝

在理解了 Vuex 的基本概念,以及所能帶來的好處之後,接下來就是將它安裝到我們的專案裡頭。

注意 - Vuex 版本選擇

與 Vue Router 一樣,為配合 Vue 3.x 的內容,本書所採用的是 Vuex v4.x 版本。 截至目前為止,最新的版本為 v4.0.0-rc.2,若讀者專案所使用的是 Vue 2.x 版本,請務必選用 Vuex v3.x (https://vuex.vuejs.org/installation.html (opens new window)),目前最新版本為 v3.6.0

另外,好消息是 Vuex v4.x 與 Vuex v3.x 的 API 完全相容,Vuex 4.x 為了配合 Vue 3.0 的發布,所以 Vuex 底層核心必須進行改寫,感謝 Vue 核心團隊的努力。

與前一章的 Vue Router 一樣,若是新專案的啟動,我們可以透過 Vue CLI 來安裝 Vuex:









 





? Check the features needed for your project:
(Press <space> to select, <a> to toggle all, <i> to invert selection)

❯◉ Choose Vue version
 ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◯ Router
 ◉ Vuex
 ◯ CSS Pre-processors
 ◯ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing
1
2
3
4
5
6
7
8
9
10
11
12
13

像這樣在安裝的時候把 Vuex 的選項選取起來就可以了,接下來就讓它自己裝。


或者,如果是已經建立好的專案,但在專案剛開始建置時並未安裝過 Vuex 也不用擔心, 只需要打開終端機並切換到專案目錄下,執行:

# 有安裝 Vue CLI 的情況
$ vue add vuex@next
1
2

若讀者朋友未安裝 Vue CLI,也可透過 npm 或 yarn 來進行安裝 Vuex:

$ npm install vuex@next --save
# 或者 yarn add vuex@next --save
1
2

當然,採用 CDN 方式的讀者也可以直接在網頁引入 <script> 標籤:

<!-- 注意這是 Vuex 4.x 的連結 -->
<script src="https://unpkg.com/vuex@next"></script>
1
2

這樣就可以使用 Vuex 了。

小提醒

使用 Vuex 需要用到 ES6 Promise,雖然說現代主流瀏覽器全部都有支援 Promise 這個特性。

但若你所使用的瀏覽器不幸地並不支援 Promise,就像 https://caniuse.com/?search=promise 所說:

Promise

也不用擔心,那麼這個瀏覽器是絕對無法使用 Vue 3.x 的 (笑)。

若讀者使用的是 Vue 2.x,則可以透過此 CDN 引入 es6-promise 的 Polyfill:

<script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.js"></script>
1

如果你是透過 Vue CLI 安裝 Vuex,那麼應該會發現 Vue CLI 已經很貼心地替我們新增了 src/store/index.js 檔案:

// src/store/index.js
import { createStore } from 'vuex'

export default createStore({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13

這就是我們專案的倉庫:store,若是採用其他方式安裝也沒關係,自己新增一個 store.js 的檔案即可。


另外,若讀者是採用 CDN 來引入 Vuex 的話,則需要改寫為:














 
 
 
 
 
 
 
 
 
 
 
 
 
 


 








<body>

  <div id="app">
    <h1>{{ message }}</h1>
  </div>

  <!-- Vue 3.x -->
  <script src="https://unpkg.com/vue@next"></script>
  
  <!-- Vuex@next -->
  <script src="https://unpkg.com/vuex@next"></script>

  <script>
    const store = Vuex.createStore({
      state: {
        message: 'Hello!'
      },
      mutations: {
        // ...
      },
      actions: {
        // ...
      },
      modules: {
        // ...
      }
    });

    const app = Vue.createApp({
      store,
      computed: {
        // Hello!
        message: () => store.state.message
      }
    }).mount('#app');
  </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

其他使用方式則是大同小異。

注意

為求程式碼撰寫格式統一,接下來本書會全面採用 Vue-CLI / webpack 的方式來進行解說 Vuex。

Last Updated: 1/3/2021, 6:10:34 PM