第12屆鐵人賽Day 13 Vuex狀態管理
昨天有聊到event bus
雖然方便,但是不容易偵錯及維護。
隨著專案變大、元件變得越來越多,元件間的架構越來越複雜,有時候會遇到多個元件需要共用同一份資料的情況…。
Eg.某一訊息app的已讀/未讀,要如何在手機版、網頁版都能一致的顯示?
在大型的Vue.js專案通常會使用Vuex
來進行狀態管理(State Management),以實現了Single source of Truth(SSOT,單一真值來源)。當我們的來源只有一個的時候,寫的程式邏輯變少,出錯的機會也就降低了~
知名的前端框架都會有狀態管理(State Management)的設計,例如:
Vuex之於Vue,
Redux之於React、
NgRx之於Angular。
從Vuex官網上的video上可以看到這張解釋Vuex
與Vue
的差異。(推薦一定要去看影片!初學者也能夠快速理解state management的設計邏輯。)
Vue和Vuex的相同點
左邊Vue
,和右邊Vuex
同樣都需要new一個instance出來。
在Vuex裡,會把這個實體取名為Store
,儲存元件中共享的資料狀態。
Vue | Vuex |
---|---|
methods 可以更新data |
action 可以更新state |
computed 可以取得data 的值 |
getters 可以取得state 的值 |
(methods和computed的比較在第六天鐵人賽的時候有介紹唷!)
相異點
然而,在Vuex
,為了確保state的一致性,我們主要透過交給mutations
這裡來追蹤、同步state的狀態,而讓action只用來呼叫mutations
而已。(mutation
這個單字就有變化、變異與轉換的意思~是不是能生動地表達跟action的差別呢?)
請注意:如果不通過mutation來改變state的話 ,狀態就不會被同步!所以請不要直接透過action去更新state。
總結:在Vuex中,元件更動狀態流程如下(圖片來自官網):
- Actions: 發出Commits呼叫Mutation。
- Mutations: 去更改 state 的方法。
今天就來致敬一下官網提供的記數器範例,並且搬到我們的Rails App吧!
實作需求:
我想透過Vuex
的狀態管理,在Root
和Counter
元件按增+
或減-
,都能同步顯示一樣的值。
Step0. 安裝並引入Vuex
首先在Rails 6專案裡利用指令yarn add vuex
安裝
yarn add vuex
//略
...
success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ vuex@3.5.1
info All dependencies
└─ vuex@3.5.1
✨ Done in 3.30s.
Step1. 寫一份Store.js
的檔案
並且透過mutations的方式更改sate裡count
的值
import Vue from 'vue/dist/vue.esm'
import Vuex from 'vuex'
Vue.use(Vuex)
// 定義一個新的 Vue Store
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
// 將state設定為參數
increment: state => state.count++,
decrement: state => state.count--
}
})
export default store;
Step2. 建立Counter
元件
引入上一步驟的的store.js
讓+
,-
按鈕可以被綁定在methods裡的increment
與decrement
函式
並且透過store.commit
送至mutations
執行
computed
的count
會根據data中的任何變化而立即重新計算
counter.vue
<template>
<div id="app">
<!-- <p>123</p> -->
<div class="main-counter">
<p>Counter元件: Vuex計數器</p>
<p class="display-count">8</p>
<p>
<button class="counter-button" @click="increment">+</button>
<button class="counter-button" @click="decrement">-</button>
</p>
</div>
</div>
</template>
<script>
import store from '../packs/store';
export default {
store,
computed: {
// 直接當作普通屬性調用,不加括號
// data中的任何變化會立即重新計算
// 會有緩存
count(){
return store.state.count
}
},
methods: {
increment(){
store.commit('increment')
},
decrement(){
store.commit('decrement')
}
}
}
</script>
Step3. 視實作的需求,我們也在其他的元件引入同一份store.js
例如:我想在Root元件也可以顯示count
的記數,就可以透過computed
return
這個store裡面的state值。
import Counter from "../components/counter"
import store from './store';
Vue.use(TurbolinksAdapter)
document.addEventListener('turbolinks:load', () => {
let el = document.querySelector("#content");
if (el){
new Vue({
store,
el,
data: {
day: "第 13 天",
topic: "Vuex: 狀態管理",
},
computed: {
count(){
return store.state.count
}
},
methods: {
increment(){
store.commit('increment')
},
decrement(){
store.commit('decrement')
}
},
components: { Counter,Foot }
})
}
})
Step4. 首頁的layout掛上Counter
元件
home.html.erb
<div id="content">
<div class="main-title">Vue.js x Rails第12屆鐵人賽專案:</div>
<div class="main-body">本日主題:</div>
<div class="main-counter">
<p>Root元件: Vuex計數器</p>
<p class="display-count">8</p>
<p>
<button class="counter-button" @click="increment">+</button>
<button class="counter-button" @click="decrement">-</button>
</p>
</div>
<Counter></Counter>
<Foot></Foot>
</div>
完成圖:
後記:在理解Vuex的mutations時,讓我聯想到Rails專案裡,紀錄schema
欄位變化的migration
遷移檔,也是為了讓不同開發者的資料庫能夠一致,多拉出了一個層級,去統一管理資料欄位的變化。(不知道這樣的譬喻恰不恰當~XD 請各位前輩指教)
明天再來講一個大重點:Vue instance的生命週期!
Ref: