Vue.js - Vuex

當專案日漸龐大而資料邏輯變得非常複雜時,過去那些Vue組件間溝通的方式可能不足以滿足我們的需求。
所以Vue.js提供了一個應用程序開發的狀態管理模式,已集中式存儲管理所有組件的狀態。
它的一大賣點是可以針對狀態做快照並可以回朔操作,也可以透過Vue的devTool監測commit以確保狀態如我們預測的變化。

下載

可以在專案中透過以下指令下載或在Vue-CLI的初始設定選擇使用Vuex。

npm install vuex --save

基本設定

新建一個store模組,若用Vue-CLI且有設定使用Vuex則會幫你預設建好該檔案。

// store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex) // 調用Vuex

export default new Vuex.Store({
state: {
// 統一管理的資料
},
getters: {
// state的派生狀態,在不變動state的前提下提供條件篩選後的資料
},
mutations: {
// 處理同步的state改變(vuex規範)
},
actions: {
// 處理非同步(vuex規範)
}
})

在Vue專案的入口文件引用store.js。

// main.js
import store from './store'

new Vue({
store, // 記得要在實體化時加入
render: h => h(App)
}).$mount('#app')

功能介紹

以下將簡單講述Vuex的核心概念。

State

Vuex採單一狀態樹-用一個Object統一包含整個Web應用程式共用的狀態,以便作為「唯一數據源(SSOT)」而存在。
注意每個Vue的專案限制只能使用一個store去存儲資料狀態,讓我們在應用程式不同的狀況下都可以任意從這個倉庫調閱資料。

// store.js
export default new Vuex.Store({
state: {
count: 0
}
})

因為在根實例註冊store,其底下之任意組件均能透過this.$store取得State的值。

// 某個組件的vue file
export default {
computed: {
count() {
return this.$store.state.count
}
}
}

Getter

我們常常會碰到一些要去篩選array或object資料的狀況,若它會很頻繁的被使用,
用mixins把篩選函式copy到各個組件會是個不聰明的方法。
Vuex提供了getter-可以視為store的計算屬性,只有當其對應到的state發生改變才會被重新計算。
getter裡的函式第一個參數為state,也可將其他getter設為第二個參數做更進一步的過濾。

// store.js
export default new Vuex.Store({
state: {
activities: [
{ id: 1, title: '彈bass', likeFlag: true },
{ id: 2, title: '跑步', likeFlag: false },
{ id: 3, title: '念日文', likeFlag: true }
]
},
getters: {
likedActivities: state => {
return state.activities.filter(activity => activity.likeFlag)
},
likedActivitiesCount: (state, getters) => {
return getters.likedActivities.length
}
}
})

在組件中取得getter的值方法如下。

// 某個組件的vue file
export default {
computed: {
likedActivities() {
return this.$store.getters.likedActivities
}
}
}

Mutation

依照Vuex官方守則的玩法,只能在提交mutation時更動state的資料,且此變動依規定必須是同步的操作。
每個mutation都會有一個string的事件類型(type)和一個回調函數(handler),
回調函數的第一個參數為state,第二個參數為payload-它允許提交時能帶入額外的參數,通常會為了使程式更易讀而把payload包裝成有語意的object。
須要特別注意array和object的變動,我個人是偏好用ES6的展開運算子去處理。

// store.js
export default new Vuex.Store({
state: {
count: 0,
activities: [],
userInfo: {
name: 'Lilybon',
id: 'lilybon777',
title: '廢文可撥仔'
}
},
mutations: {
increment(state) {
state.count++
},
pushActivities(state, payload) {
let activities = payload.activities
state.activities = [...state.activities, activities]
},
updateUserTitle(state, payload) {
let title = payload.title
state.userInfo = {...state.userInfo, title}
// Vue.set(userInfo, 'title', title)
}
}
})

由組件提交mutation用commit,每筆commit可以被Vue的devTool觀測到紀錄,
透過devTool可以檢閱每次commit後的state資料、改動時間,甚至可以回朔commit操作,讓debug變得稍微快樂了一些。

// 某個組件的vue file
export default {
methods: {
increment() {
this.$store.commit('increment')
},
updateUserTitle(title) {
this.$store.commit('updateUserTitle', { // 自訂義語意的payload格式
title
})
}
}
}

Action

action依照規定只能提交mutation而不能竄改資料,
也就是它不會直接進行state的改變操作,而只允許透過傳遞payload給mutation讓它來進行資料改動。
它主要的功能是在執行任意非同步的操作。
下面以一個setTimeout和axios request作範例。

// store.js
import axios from 'axios'

export default new Vuex.Store({
state: {
boredApiDomain: 'https://www.boredapi.com',
count: 0,
activity: []
},
mutations: {
increment(state) {
state.count++
},
updateActivities(state, payload) {
let activity = payload.activity
state.activity = {...activity}
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
},
requestActivity({ commit, state }, payload) {
// 可將state當參數傳入但切記不能直接對state操作
// 第二個參數為可帶入之自訂義荷載
if(!state.activities.length){
let method = payload.method
axios.get(`${ boredApiDomain }${ method }`).then(res => {
let activity = res.data
commit('updateActivities', {
activity
})
})
}
}
}
})

由組件發起action用dispatch。

// 某個組件的vue file
export default {
methods: {
incrementAsync() {
this.$store.dispatch('incrementAsync')
},
requestActivity(method) {
this.$store.dispatch('requestActivity', { // 自訂義語意的payload格式
method
})
}
}
}

在組件間分發

載入輔助函數將組件的methods函式映射至store的mutation和dispatch,
將組件的computed屬性映射至store的state和getter,
避免每次開一個新組件要就要重復寫一次對應的串接方法而感到煩躁無比。

// 某個組件的vue file
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'

export default {
computed: {
...mapState(['count']), //將"this.count()"映射為"this.$store.state.count"
...mapGetters([ // 也可以使用字串array映射多個對應屬性
'likedActivities',
'dislikeActivities'
])
},
methods: {
...mapMutations({ // 將"this.add()"映射為"this.$store.commit.increment"
add: 'increment'
}),
...mapActions({ // 也可以使用多個自訂義的函數名稱對應實際存在的action
addAsync : 'incrementAsync',
requestRNGAct : 'requestActivity'
})
}
}

以上就是Vuex的介紹,心動不如趕快行動,在專案還沒大爆炸前改用它來當你的統一資料管理站吧。

參考