延續之前的主題 Vuetify 中用到的技術大方向有:
今天要分享的是 Vuex,並且解釋在 Vuetify 的模板 WebpackSSR 是如何使用 Vuex。
還沒看前幾篇可以先從 Day19: Vuetify 開始閱讀。
Vuex is a state management pattern + library for Vue.js applications.
Vuex 是一個專門為 Vue.js 開發的狀態管理模式,採用集中式儲存管理應用的所有 Vue Instance 或 Component 的狀態,並以相應的規則保證狀態以可預期的方式變化。
當很多的 Component 需要共用同一個狀態的時候,或者是打算開發大型的 SPA 時,可以考慮使用 Vuex。
但如果只是簡單的應用,基本上用 global event bus 的方法即可。
直接看一個範例,記得要先引入 vue.js 和 vuex。
透過 new Vuex.Store({ options })
建立一個 store
,store
即為一個 Vuex 應用的核心,也可以想像成一個容器,內容包含了應用程式中大部分的狀態。
創建的過程非常簡單,這個範例僅提供了一個初始的state
和mutations
。
<div id="app">
<p>{{ count }}</p>
<p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</p>
</div>
// make sure to call Vue.use(Vuex) if using a module system
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment: state => state.count++,
decrement: state => state.count--
}
})
new Vue({
el: '#app',
computed: {
count () {
return store.state.count
}
},
methods: {
increment () {
store.commit('increment');
// 也可以直接修改但這樣就喪失使用 Vuex的意義
// store.state.count++
},
decrement () {
store.commit('decrement');
// 也可以直接修改但這樣就喪失使用 Vuex的意義
// store.state.count--
}
}
})
附上 codepen 範例 Vuex: Quick Start
注意:
store
中的狀態發生變化時,那麼有依賴這些狀態的 Vue Component 也會跟著高效更新。store
狀態的方式是透過(commit)mutation,雖然我們可以透過store.state.count
直接更改store
當中的狀態,但這是不對的。actions
來提交mutation
和處理非同步操作,若直接以修改store.state.count
的方式,將會喪失了使用 Vuex 的核心意義。看完範例再強調一次:
Vuex is a state management pattern + library for Vue.js applications.
很重要所以寫兩次的原因在於,我個人糾結於官網的這段話:
你不能直接改變 store 中的狀態。改變 store 狀態的唯一途徑就是提交 (commit) mutation。這樣使得我們可以方便地跟踪每一個狀態的變化,從而讓我們能夠實現一些工具幫助我們更好地了解我們的應用。
但實際上卻可以直接透過store.state.count
更改狀態,有潔癖的我天真的以為這樣的寫法應該是要報錯的啊。
深深地認為官網這段話是否不夠精確,或者是mutation
背後有甚麼我不明白的機制?
歡迎打臉。
更正(2018/01/05):
Vuex 有提供一個嚴格模式,可使直接修改store.state.count
時報錯誤訊息。
簡單修改上面的範例如下:
const store = new Vuex.Store({
stict: true,
state: {
count: 0
},
mutations: {
increment: state => state.count++,
decrement: state => state.count--
}
})
new Vue({
el: '#app',
computed: {
count () {
return store.state.count
}
},
methods: {
increment () {
store.state.count++
},
decrement () {
store.state.count--
}
}
})
附上 codepen 範例 Vuex: Strict
Quick View:
Vuex 使用單一狀態樹,用一個物件包含所有應用的狀態,因此 Vuex 以一個「唯一數據源(SSOT)」而存在。也意味著,每個 Vue Instance 僅包含一個 store 實例。
注意:
使用 Vuex 並不意味著你需要將所有的狀態放入 Vuex。雖然將所有的狀態放到 Vuex 會使狀態變化更顯式和易調試,但也會使代碼變得冗長和不直觀。如果有些狀態嚴格屬於單個組件,最好還是作為組件的局部狀態。你應該根據你的應用開發需要進行權衡和確定。
另外,當一個 Component 需要獲取許多狀態的時候,可以透過 mapState 簡化程式碼,這個部分就不多做解釋,可以直接去官網看。
可以把 Vuex 的 getter 想像成 Vue 的 computed。getter接受state作為第一個參數,且getter回傳的值會根據他依賴的state被更改時重新計算。而getter也可以接受其他getter作為第二個參數。
例如:
在 Vuex 實例中定義 getters:
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
在 Vue 實例中使用 getters:
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}
也可以透過讓getter回傳一個函式,實現給getters傳入參數的功能,例如:
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
另外,當一個 Component 需要獲取許getters的時候,可以透過 mapGetters 簡化程式碼,這個部分就不多做解釋,可以直接去官網看。
更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
state.count++
}
}
})
可以透過store.commit('increment')
執行 mutation。
另外,也可以對store.commit
傳入參數,而這個參數通常為一個物件包含所需的值。
例如:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
actions 類似於 mutations,不同在於:
Action 函數接受一個與store實例具有相同方法和屬性的context對象,因此可以利用context.commit
提交一個mutation。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
可以利用 ES6 的解構來簡化以上程式碼:
actions: {
increment ({commit}) {
commit('increment')
}
}
可以透過store.dispatch('increment')
執行 action。
由於 Vuex 使用單一狀態樹,因此當應用變得非常複雜時,store物件就會變得非常肥大難以管理,因此可以將 store 分割成多個 module,每個 module 各自用有各自的 state, getters, mutations, actions,甚至可以再模塊中再引入子模塊。
例如:
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA.state
store.state.b // -> moduleB.state
個人還沒使用過 module,就不多加贅述,有興趣自己到官網看看。
在對於 Vuex 有基本的了解後,可以來解釋一下在 Vuetify 的模組 WebpackSSR 中,如何使用 Vuex。
首先回顧一下 WebpackSSR 的目錄結構,方便對照:
在 Vuetify WebpackSSR(以下簡稱VW)的目錄結構中,所有關於 vuex 的東西都放在 store
資料夾。透過store
中的 index.js 建立 createStore,並在assets
中的app.js引入,並且將實體化的Store物件作為屬性傳入 Vue instance 中。
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export function createStore () {
return new Vuex.Store({
state: {},
actions: {},
mutations: {},
getters: {}
})
}
VW 這個模板中只有初始化 Vuex,而其中的內容都交由開發者自定義,因此 state, actions, mutations, getters 都暫時為空物件。
assets/app.js
import Vue from 'vue'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.css'
import App from './App.vue'
import Components from 'components/_index'
import { createStore } from 'store/index'
import { createRouter } from 'router/index'
import { sync } from 'vuex-router-sync'
Vue.use(Vuetify)
Object.keys(Components).forEach(key => {
Vue.component(key, Components[key])
})
export function createApp (ssrContext) {
const store = createStore() // 實體化 vuex
const router = createRouter() // 實體化 router = new Router({options})
sync(store, router)
// 透過 vuex-router-sync 同步 vuex 和 vue-router,會同步的屬性有 {
// path: '',
// query: null,
// params: null
// }
// 主要是將 vue-router 的狀態放進 vuex 的 state 中,透過改變 state 進行路由的操作。
// 這使我們可以輕鬆透過以下方法取得或操作路由設定:
// store.state.route.path // current path (string)
// store.state.route.params // current params (object)
// store.state.route.query // current query (object)
const app = new Vue({
router,
store,
ssrContext, // 之後篇幅會補充
render: h => h(App)
// h => h(App)中的App為資料夾assets中的App.vue,App.vue是這整個SPA最核心基礎的頁面組成。
})
// 將 router, store, ssrContext, render 傳入選項物件中
return { app, router, store }
}
在 Vuex 裡面有個東西叫「嚴格模式」
const store = new Vuex.Store({
// ...
strict: true
});
開啟嚴格模式後,當你在 methods
直接對 store.state
操作而不是透過 mutation
處理的時候就會報錯。
感謝 Kuro 大神專業打臉,馬上來更正內文。
請問有針對 Vuex 的範例操作可以參考嗎?發現 Vue 官網 Vuex 系列的範例沒有操作只有原始碼 ...