Vuex 是什么?
** 官方解釋:Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式**。它采用集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化。Vuex 也集成到 Vue 的官方調試工具 devtools extension,提供了諸如零配置的 time-travel 調試、狀態快照導入導出等高級調試功能。
vuex其實就是用一個全局的變量保存了Vue項目中的所有的公共數據,類似與在前端這塊放了一個數據庫,大家都可以在這里存數據,刪數據,改數據,讀數據,是不是有點熟悉:增,刪,改,查;不過這個全局的變量給他定義了一個固定的名字就叫:store
(倉庫),是不是很形象,而這個倉庫里面裝數據的袋子就是state
,加工數據的機器就叫做:mutations
,操作機器的工人就叫做:actions,把數據裝起來取走的卡車就叫做:getters
;
所以一個簡單的vuex就是:
new vuex.store({
state,
mutations,
actions,
getters
})
Vuex 特點
Vuex 的狀態存儲是響應式的。當 Vue 組件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那么相應的組件也會相應地得到高效更新。
你不能直接改變 store 中的狀態。改變 store 中的狀態的唯一途徑就是顯式地提交(commit) mutations。這樣使得我們可以方便地跟蹤每一個狀態的變化,從而讓我們能夠實現一些工具幫助我們更好地了解我們的應用。
為什么使用Vuex?
當你要開發一個大型的SPA應用的時候,會出現:多個視圖公用一個狀態、不同視圖的行為要改變同一個狀態的情況,遇到這種情況的時候就需要考慮使用Vuex了,它會把組件的共享狀態抽取出來,當做一個全局單例模式進行管理,這樣不管你何時何地改變狀態,都會通知到使用該狀態的組件做出相應的修改;
Vuex實例
import Vue from 'vue';
import Vuex form 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
這就是一個vuex的最簡單實例,store就是組件中的共享狀態,而改變狀態的方法(其實是一個對象包含很多方法,但都是來改變store的
)叫做:mutations;
需要特變注意的是只能通過mutations改變store的state的狀態,不能通過store.state.count = 5;直接更改(其實可以更改,不建議這么做,不通過mutations改變state,狀態不會被同步
)。
使用store.commit方法觸發mutations改變state:
store.commit('increment');
console.log(store.state.count) // 1
這樣一個簡簡單單的Vuex應用就實現了。
在Vue組件里使用Vuex
Vuex的狀態獲取是一個方法,當Vuex狀態更新時,相應的Vue組件也要更新,所以Vue應該在計算屬性(computed
)獲取state;
// Counter 組件
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return store.state.count;
}
}
}
上面的例子是直接操作全局狀態store.state.count,那么每個使用該Vuex的組件都要引入。為了解決這個,Vuex通過store選項,提供了一種機制將狀態從根組件注入到每一個子組件中。
// 根組件
import Vue from 'vue';
import Vuex form 'vuex';
Vue.use(Vuex);
const app = new Vue({
el: '#app',
store,
components: {
Counter
},
template: `
<div class="app">
<counter></counter>
</div>
`
})
通過這種注入機制,就能在子組件Counter通過this.$store訪問:
// Counter 組件
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
mapState函數
computed: {
count () {
return this.$store.state.count
}
}
這樣通過count計算屬性獲取同名state.count屬性,是不是顯得太重復了,我們可以使用mapState函數簡化這個過程。
import { mapState } from 'vuex';
export default {
computed: mapState ({
count: state => state.count,
countAlias: 'count', // 別名 `count` 等價于 state => state.count
})
}
還有更簡單的使用方法:
computed: mapState([
// 映射 this.count 為 store.state.count
'count'
])
Getters對象
如果我們需要對state對象進行做處理計算,如下:
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
如果多個組件都要進行這樣的處理,那么就要在多個組件中復制該函數。這樣是很沒有效率的事情,當這個處理過程更改了,還有在多個組件中進行同樣的更改,這就更加不易于維護。
Vuex中getters對象,可以方便我們在store中做集中的處理。Getters接受state作為第一個參數:
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
在Vue中通過store.getters對象調用。
computed: {
doneTodos () {
return this.$store.getters.doneTodos
}
}
Getter也可以接受其他getters作為第二個參數:
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
mapGetters輔助函數
與mapState類似,都能達到簡化代碼的效果。mapGetters輔助函數僅僅是將store中的getters映射到局部計算屬性:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用對象展開運算符將 getters 混入 computed 對象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
上面也可以寫作:
computed: mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
所以在Vue的computed
計算屬性中會存在兩種輔助函數:
import { mapState, mapGetters } form 'vuex';
export default {
// ...
computed: {
mapState({ ... }),
mapGetter({ ... })
}
}
Mutations
更改Vuex的store中的狀態的唯一方法就是mutations
.
每一個mutation
都有一個事件類型type
和一個回調函數handler
。
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 變更狀態
state.count++
}
}
})
調用mutation,需要通過store.commit方法調用mutation type:
store.commit('increment')
Payload 提交載荷
也可以向store.commit傳入第二參數,也就是mutation的payload:
mutaion: {
increment (state, n) {
state.count += n;
}
}
store.commit('increment', 10);
單單傳入一個n,可能并不能滿足我們的業務需要,這時候我們可以選擇傳入一個payload對象:
mutation: {
increment (state, payload) {
state.totalPrice += payload.price + payload.count;
}
}
store.commit({
type: 'increment',
price: 10,
count: 8
})
mapMutations函數
不例外,mutations也有映射函數mapMutations,幫助我們簡化代碼,使用mapMutations輔助函數將組件中的methods映射為store.commit調用。
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment' // 映射 this.increment() 為 this.$store.commit('increment')
]),
...mapMutations({
add: 'increment' // 映射 this.add() 為 this.$store.commit('increment')
})
}
}
注 Mutations必須是同步函數(即沒有異步操作
)。
如果我們需要異步操作,Mutations就不能滿足我們需求了,這時候我們就需要Actions了。
Aciton對象
Action 類似于 mutation
,不同在于:
- Action 提交的是
mutation
,而不是直接變更狀態。 - Action 可以包含任意異步操作(彌補了
mutation
不能有異步操作的不足)。
讓我們來注冊一個簡單的 action:
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) { //此處的context具有與store實例相同的方法和屬性
context.commit('increment')
}
}
})
Action 函數接受一個與 store
實例具有相同方法和屬性的 context
對象,因此你可以:
- 調用
context.commit
,提交一個mutation
; - 通過
context.state
和context.getters
來獲取state
和getters
。
當我們在之后介紹到Modules(https://vuex.vuejs.org/zh-cn/modules.html)
時,你就知道 context 對象為什么不是 store 實例本身了。
實踐中,我們會經常會用到 ES2015 的 參數解構 來簡化代碼(特別是我們需要調用 commit很多次的時候
):
actions: {
increment ({ commit }) {
commit('increment')
}
}
分發 Action
Action 通過 store.dispatch (與commit相對應
)方法觸發:
store.dispatch('increment')
乍一眼看上去感覺多此一舉,我們直接分發 mutation
豈不更方便?實際上并非如此,還記得mutation
必須同步執行這個限制么?Action 就不受約束!我們可以在 action
內部執行異步操作:
actions: {
incrementAsync ({ commit }) {
setTimeout(() => { //模擬異步請求
commit('increment')
}, 1000)
}
}
Actions 支持同樣的載荷方式和對象方式進行分發:
// 以載荷形式分發
store.dispatch('incrementAsync', {
amount: 10
})
// 以對象形式分發
store.dispatch({
type: 'incrementAsync',
amount: 10
})
來看一個更加實際的購物車示例,涉及到調用異步 API 和 分發多重 mutations:
actions: {
checkout ({ commit, state }, products) {
// 把當前購物車的物品備份起來
const savedCartItems = [...state.cart.added]
// 發出結賬請求,然后樂觀地清空購物車
commit(types.CHECKOUT_REQUEST)
// 購物 API 接受一個成功回調和一個失敗回調
shop.buyProducts(
products,
// 成功操作
() => commit(types.CHECKOUT_SUCCESS),
// 失敗操作
() => commit(types.CHECKOUT_FAILURE, savedCartItems)
)
}
}
注意我們正在進行一系列的異步操作,并且通過提交 mutation 來記錄 action 產生的副作用(即狀態變更)。
在組件中分發 Action
action為改變狀態(數據)的方法,所以應該放在Vue組件的
methods
中;
你在組件中使用 this.$store.dispatch('xxx')
分發action
,或者使用 mapActions
輔助函數將組件的 methods
映射為 store.dispatch
調用(需要先在根節點注入 store
):
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment' // 映射 this.increment() 為 this.$store.dispatch('increment')
]),
...mapActions({
add: 'increment' // 映射 this.add() 為 this.$store.dispatch('increment')
})
}
}
組合 Actions
Action 通常是異步的,那么如何知道 action 什么時候結束呢?更重要的是,我們如何才能組合多個 action,以處理更加復雜的異步流程?
首先,你需要明白 store.dispatch
可以處理被觸發的action
的回調函數返回的Promise
,并且store.dispatch
仍舊返回Promise
:
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
現在你可以在Vue組件中:
store.dispatch('actionA').then(() => {
// ...
})
在另外一個 action 中也可以:
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
最后,如果我們利用 async / await 這個 JavaScript 即將到來的新特性,我們可以像這樣組合 action:
// 假設 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
一個 store.dispatch在不同模塊中可以觸發多個 action 函數。在這種情況下,只有當所有觸發函數完成后,返回的 Promise 才會執行。
Modules
Vuex只能有一個單一的store對象,但是當應用變得龐大復雜的時候store就可能變得非常的臃腫;
所以為了解決這個問題Vuex增加了** 模塊(module
)**的概念,也就是說你可以在這個store對象里面再建子對象了,而且這些子對象都有: state
、mutation
、action
、getter
、嵌套子模塊(再在子模塊中從上至下進行同樣方式的分割)
;
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 的狀態
store.state.b // -> moduleB 的狀態
模塊的局部狀態
對于模塊內部的 mutation 和 getter,接收的第一個參數是模塊的局部狀態對象。
const moduleA = {
state: { count: 0 },//模塊的局部狀態對象
mutations: {
increment (state) {
// 這里的 `state` 對象是模塊的局部狀態
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
同樣,對于模塊內部的 action,局部狀態通過 context.state 暴露出來, 根節點狀態則為 context.rootState:
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
對于模塊內部的 getter,根節點狀態會作為第三個參數暴露出來:
const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
文章里面代碼直接使用的是文檔里面的,感覺文檔也不是十分的難理解,后面的命名空間,暫時沒有整理,有時間看下后續整理吧,大家有什么問題可以在評論區一起討論,;-)
** 文章部分引用:**
https://yeaseonzhang.github.io/2017/03/16/Vuex-%E9%80%9A%E4%BF%97%E7%89%88/
https://vuex.vuejs.org/zh-cn/modules.html