文章目的
之前在學習 vue 的資料傳遞時,是使用 Event Bus 的方式來做傳遞,但當專案較大時,Event Bus 會較難管理資料,這時我們就可以利用 Vuex 來達成資料管理與傳遞的目的,本文將介紹 Vuex 與說明使用方法。
Vuex 介紹
Vuex 和 Event Bus 不一樣的地方是它是一個儲存庫的概念,儲存庫中將會為每一個步驟做分類與分工並讓所有的元件做使用,簡單來講就是先將資料做個統整再做傳遞。
Vuex 方法
- state:如同元件中的 data,負責管理資料。
- actions:如同元件中的 methods,負責處裡非同步事件,或是取得遠端資料等。
- getter:如同元件中的 computed,在資料呈現之前做過濾的動作。
- mutations:這是 Vuex 新增的方法,改變資料內容的方法,在以前我們要改變資料狀態時都會直接在 methods 做變更,但在 Vuex 不同,actions 並不會做資料的變更,而是在 mutations 才會做資料狀態的變更。
Vuex 運作方法
在 Vue 的元件中,我們透過 dispatch 來觸發 actions,actions 就會去取得遠端資料,並透過 commit 的方式呼叫 mutations,最後透過 mutations 來改變 state 中的資料狀態並反映給元件。
這邊有一點要注意,當要使用一些非同步的行為時(AJAX、Settimeout 等),請在 actions 中就完成它。
Vuex 簡單範例,透過 Vuex 改變資料狀態
現在我們就來看看 Vuex 各方法的寫法吧。
範例狀況
現在我們的專案中有讀取的動畫功能,因為很多元件在取得資料時都會需要使用這功能,因此我們打算利用 Vuex 統一管理動畫功能。
我們利用 isLoading = true or false
來控制動畫的出現與消失。
創造我們的 Vuex 儲存庫
我們先在 src
資料夾中創造一個名為 store
的資料夾,並在此資料夾中創造一個名為 index.js
的檔案,這個檔案將為我們管理 Vuex。
在檔案中我們先將
vue
和vuex
import 進來,程式碼如下:1
2
3
4import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)另外在 main.js(進入點) 也要做 import 的動作,並把 store 也 import 進來,程式碼如下:
1
2
3
4
5
6
7
8
9
10import Vuex from 'vuex'
import store from './store'
Vue.use(Vuex)
new Vue({
router,
store, //記得這裡也要補上 store 喔
render: h => h(App)
}).$mount('#app')接著我們在
export default
中輸入我們的方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15export default new Vuex.Store({
state: {
isLoading: false
},
actions: {
updateLoading (context, status) {
context.commit('LOADING', status)
}
},
mutations: {
LOADING (state, status) {
state.isLoading = status
}
}
})這邊將為 actions 和 mutations 做解釋
actions:創建一個 function,function 中擁有兩個參數,分別是 context
和 payload(載核)
, context
是必要的參數, payload
則是我們自定義的參數(名稱也可自訂,以此範例來說就是 status
),接著使用 context.commit
觸發 mutations 並把載核傳遞給它。
mutations:一樣創建一個 function,當此 function 被觸發時就會對 state 中的資料做變更。
統整:在元件中因為某個事件透過 dispatch
觸發了 updateLoading
這個 action,觸發的同時將 status 的資料傳遞進來,接著 updateLoading
透過 context.commit
觸發名為 LOADING
的 mutations 並將 status 資料傳遞給它,最後在名為 LOADING
的 mutations 中將 state 中的資料狀態變更,資料變更的同時會反應回元件上。
元件 Vuex 設定
因為是整個專案都會用到此動畫效果,因此我們將動畫程式碼放在我們的 App.vue 中就可以反映在每個元件上。
那因為資料的變更也是反應在 App.vue(根元件),因此我們可以利用 computed
來監控 Vuex 中的這個資料,程式碼如下:
1 | computed: { |
接著我們需要改寫的就是 methods 中觸發效果的寫法,程式碼如下:
1 | vm.$store.dispatch('updateLoading', true) |
透過 dispatch
觸發 updateLoading
這個 actions,並將 payload(true or false) 傳入,接著系統就會執行我們剛剛說的 Vuex 內部流程。
為 Vuex 加入嚴謹模式來檢視錯誤
我們另外可以在 Vuex 中加入嚴謹模式,讓我們的程式碼撰寫都在規範中,加入方式只需在 state 之前寫入以下程式碼即可:
1 | strict: true, |
使用 actions 取得遠端資料
剛剛有說過若要在 Vuex 中執行非同步行為我們就需要在 actions 中執行完這個動作。
現在將要介紹如何在 actions 中執行 AJAX 的行為。
在 Vuex 中執行 AJAX 的好處是我們可以統一管理這個行為,且較大型網站若有很多元件都需要用到此 AJAX,也可以隨時調用,不需要在元件內重複撰寫。
範例狀況
現在我們要接取一支可以取得商品資料的 api, 抓取到的 api 資料皆儲存在 products
這個陣列裡,以下是程式碼:
1 | export default new Vuex.Store({ |
我們的目的是要把 AJAX 的資料儲存到 state 的 products 裡,因此我們先在 actions 利用 getProducts
這個 function 去接取 api 並把抓取到的資料透過 commit 傳遞給 PRODUCTS
,PRODUCTS
收到資料後,再將資料儲存進 state 的 products 裡。
axios 解釋
這邊使用的接取 api 方法是 Vue axios,但你會發現我們上列的程式碼,接取 api 時不是這樣寫 this.$http.get(url)
,原因是現在 AJAX 的行為是在 Vuex 中進行,this 是代表元件,但現在並非在元件內,因此才會改寫,那改寫步驟如以下:
- 將
axios import from 'axios'
寫入 Vuex 裡。 - 把
this.$http
改寫成axios
。
我們是直接透過掛載 axios 的方法並呼叫它,而非像一開始用 Vue 呼叫。
元件改寫
我們元件中一樣要有事件觸發 AJAX 的行為,一樣是使用 dispatch
做觸發,寫法如下:
1 | getProducts () { |
一樣的我們要將 products 這個陣列內容反應在元件中,我們透過 computed 來調用,寫法如下:
1 | products () { //寫在 computed 中 |
利用 actions 傳遞參數
這邊要說明的是,actions 方法中只能傳遞一個參數,當我們今天要傳遞複數個參數時,就得利用物件的方法做傳遞,程式碼如下:
1 | this.$store.dispatch('actions 方法', { 參數1, 參數2 }) |
另外若我們在執行某個 actions 方法時,需要執行另外一個 actions 方法可以利用 commit.dispatch('actions 方法名', 參數)
,利用這種方法調用其他 actions。
getters 寫法
前面有提到 getters 相當於 computed 我們也可以將部分寫法改成用 getters 替代,這邊示範將前幾個範例改寫。
我們在 Vuex 中新增一個名為 getters
的物件,並將要 computed 回傳的資料寫入,程式碼如下:
1 | getters: { |
這邊回傳前面範例的 products 和 categories。
接著我們回到元件中改寫程式碼,我們先將 getters import 進元件內 import { mapGetters } from 'vuex'
,接著在 computed 做改寫,改寫成 ...mapGetters (['products', 'categories'])
,陣列中是我們 Vuex 中 getters 的方法。
另外,我們也可以將 actions 做改寫,只要在 import 中把 actions import 進來 import { mapGetters, mapActions } from 'vuex'
,並在 methods 將方法改寫成 ...mapActions (['getProducts'])
,一樣的陣列中是我們的 actions 方法名。
這邊有一點要注意的是用這方法取用 actions 時不適用在需要傳遞參數的 actions。
Vuex 模組化
你可以發現到在前面的範例中我們已經在 Vuex 中寫入了很多程式碼,但這只是一個小小的範例而已,若遇到更大型專案時,模組化的概念就會顯得相當重要。
我們可以把 Vuex 中管理的行為再做進一步的拆分,使我們更加方便管理。
這邊將示範將範例中取得商品的方法做拆分。
- 我們先在 store 資料夾中再新增一個叫做
products.js
的檔案,並把 getProducts 的 actions 和相關參數從index.js
中拆出來並寫進products.js
中。 - 接著我們利用 import 將
products.js
的內容載入至index.js
:import productsModule from './products'
,然後在 index 中新增一個名為modules
的物件,並把productsModule
加入。1
2
3modules: {
productsModule
}加強觀念
在 Vuex 中 state 是屬於模組區域變數,裡頭的資料只會在該模組中才能做使用,但是 actions, mutations 和 getters 屬於全域變數。
全域變數好處是在小型專案時可以節省開發時間,但在大型專案中就會建議使用區域變數,避免衝突(當有相同方法名的時候就會產生衝突)。
如何取得拆分模組中的 state 區域變數(元件中)
這方法很簡單,只需要將程式碼這樣改寫即可:this.$store.state.productModule.products
將模組內全域變數改成區域變數
若我們要避免衝突,將全域變數變成區域變數,可以在該模組的 export default 底下寫入這行程式碼:namespaced: true
。
變成區域變數之後我們要如何調用它呢?延續前面的調用方法我們可以這樣寫:
1 | ...mapActions('productModule', ['getProducts']) // 第一種方法,在第一個參數中寫入要調用的模組名 |
讓特定行為變為全域調用
若我們要某方法可以全域調用,像是範例的讀取動畫效果,因為變數是存在 index 裡,若我們要在 products 中調用可以這樣寫:
1 | context.commit('LOADING', true, { root: true }) |