本文對 Vuex 官方文檔重新組織編排,希望正在學(xué)習(xí) Vue 的同學(xué)們,在閱讀后可快速使用 Vuex。
開始使用 Vuex,把狀態(tài)拿到應(yīng)用外部管理,Vuex 管這個(gè)管理狀態(tài)的玩意叫 Store,一個(gè)完全獨(dú)立的應(yīng)用,他只負(fù)責(zé)狀態(tài)管理。嘗試把 Vuex 應(yīng)用和 Vue 應(yīng)用劃清界限,
- 一個(gè) Vuex 應(yīng)用,做狀態(tài)管理,可以理解是 Model 層
- 一個(gè) Vue 應(yīng)用,僅負(fù)責(zé)數(shù)據(jù)展示,純純的 View 層
Vuex 應(yīng)用
所謂狀態(tài)管理,無非就是定義狀態(tài),修改狀態(tài)。
定義 state
在 Vuex 里定義狀態(tài),我們需要 new 一個(gè) Store 出來,每一個(gè) Vuex 應(yīng)用的核心就是 store(倉庫)。
// store.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 0
}
});
export default store;
以上代碼創(chuàng)建了一個(gè) store,store.state 里定義了狀態(tài)。與在 Vue 里定義 data 沒有任何區(qū)別,
下面修改狀態(tài)。
直接修改
如果你想快點(diǎn)用上 Vuex,你可以在組件里直接修改 store 里的 state,(直接修改的意思就是,用點(diǎn)操作修改)
state.count = 2;
雖然這樣可以正常工作,但在嚴(yán)格模式下會(huì)報(bào)錯(cuò),更改 store 中的狀態(tài)的唯一方法應(yīng)該是提交 mutation。
嚴(yán)格模式
開啟嚴(yán)格模式,僅需在創(chuàng)建 store 的時(shí)候傳入 strict: true:
const store = new Vuex.Store({
strict: true
});
在嚴(yán)格模式下,無論何時(shí)發(fā)生了狀態(tài)變更且不是由 mutation 函數(shù)引起的,將會(huì)拋出錯(cuò)誤。
mutation
更改 Vuex 的 store 中的狀態(tài)的唯一方法是提交 mutation。
在Vuex.Store的構(gòu)造器選項(xiàng)中,有一個(gè) mutation 選項(xiàng),這個(gè)選項(xiàng)就像是事件注冊:key 是一個(gè)字符串表示 mutation 的類型(type),value 是一個(gè)回調(diào)函數(shù)(handler),
這個(gè)回調(diào)函數(shù)就是我們實(shí)際進(jìn)行狀態(tài)更改的地方,它有兩個(gè)入?yún)ⅲ谝粋€(gè)參數(shù)是 state,就是 store 里的 state;第二個(gè)參數(shù)是 Payload,這是提交 mutation 時(shí)候額外傳入的參數(shù)。
定義 mutation
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment(state) {
state.count++; // 變更狀態(tài)
}
}
});
提交 mutation
上面定義了 mutation,要喚醒一個(gè) mutation handler,唯一的接口是store.commit,如果你熟悉事件監(jiān)聽,commit 就類似于 Vue 的$emit,jquery 的trigger。
可想而知,commit 方法傳參必須至少有一個(gè)能區(qū)分 mutation 的唯一標(biāo)識(shí),這個(gè)標(biāo)識(shí)就是 type,
commit 參數(shù)是對象
當(dāng) commit 的參數(shù)是一個(gè)對象的時(shí)候,對象里必須要有 type 字段,除了 type 字段,你還可以添加額外任意字段為載荷(payload)。
// 對象風(fēng)格的提交方式
store.commit({
type: "increment",
amount: 10
});
type 做第一參數(shù)
把 type 和 payload 分開也是個(gè)不錯(cuò)的選擇,可以把 type 單獨(dú)拿出來當(dāng)?shù)谝粋€(gè)參數(shù),以commit(type,[payload])的形式提交,官方稱此為以載荷形式提交。
store.commit("increment");
store.commit("increment", 10);
store.commit("increment", { count: 2 });
在大多數(shù)情況下,payload 應(yīng)該是一個(gè)對象,這樣可以包含多個(gè)字段并且記錄的 mutation 會(huì)更易讀。
到這里我們已經(jīng)知道如何定義 mutation 和提交 mutation 了,commit 接口很簡單,但在哪里使用呢?有兩個(gè)地方用
- Vue 應(yīng)用的組件里,在組件里調(diào)用
store.commit。 - 還有 store 的 action 里。
Action
狀態(tài)管理不過就是定義 state 和修改 state,mutation 已經(jīng)可以修改 state 了,為什么還需要 action?
同步和異步
在 Vuex 中,mutation 都是同步事務(wù),在 mutation 中混合異步調(diào)用會(huì)導(dǎo)致你的程序很難調(diào)試。
例如,當(dāng)你調(diào)用了兩個(gè)包含異步回調(diào)的 mutation 來改變狀態(tài),你怎么知道什么時(shí)候回調(diào)和哪個(gè)先回調(diào)呢?這就是為什么我們要區(qū)分這兩個(gè)概念。
Action 確實(shí)和 mutation 很類似,不同在于:
- Action 提交的是 mutation,而不是直接變更狀態(tài)。
- Action 可以包含任意異步操作。
注冊 action:
注冊 action 就跟定義 mutation 一樣,除了 handler 的入?yún)⒉煌?/p>
Action 函數(shù)的入?yún)⑹且粋€(gè)與 store 實(shí)例具有相同方法和屬性的 context 對象。因此可以
-
context.commit提交一個(gè) mutation, -
context.state獲取 state。 -
context.getters獲取 getters。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
}
},
actions: {
increment(context) {
context.commit("increment");
}
}
});
分發(fā) Action
Action 通過 store.dispatch方法觸發(fā):
// 以載荷形式分發(fā)
store.dispatch("incrementAsync", {
amount: 10
});
// 以對象形式分發(fā)
store.dispatch({
type: "incrementAsync",
amount: 10
});
組合 Action
store.dispatch 可以處理被觸發(fā)的 action 的處理函數(shù)返回的 Promise,并且 store.dispatch 仍舊返回 Promise,因此,通過 async / await,很方便控制流程
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
表單處理
表單的問題在于使用v-model時(shí)候,v-model會(huì)試圖直接修改 state,而 Vuex 在嚴(yán)格模式下是不允許直接修改 state 的。
很容易解決,只要我們把修改 state 的行為按 Vuex 的要求以commit mutation 方式修改即可。
有兩種方式。
用“Vuex 的思維”解決
拋棄v-model指令,自己去實(shí)現(xiàn)v-model雙向綁定,非常簡單,
-
input標(biāo)簽的value屬性綁定對應(yīng)從 store 中映射來的計(jì)算屬性, - 監(jiān)聽 input 事件,用 commit mutation 的方式,修改 store 里的 state。
<input :value="message" @input="updateMessage" />
computed: {
...mapState({
message: state => state.obj.message
})
},
methods: {
updateMessage (e) {
this.$store.commit('updateMessage', e.target.value)
}
}
store 中的 mutation 函數(shù):
mutations: {
updateMessage (state, message) {
state.obj.message = message
}
}
用 Vue 計(jì)算屬性解決
如果堅(jiān)持使用v-model,可以在 Vue 里對v-model綁定的計(jì)算屬性設(shè)置 set 行為。
<input v-model="message" />
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
使用 Vuex 進(jìn)行狀態(tài)管理,到此結(jié)束。
從 flux 架構(gòu)來看,三個(gè)核心,State,Mutation,Action,已經(jīng)足夠了,總結(jié)一下其實(shí)很簡單,同步 commit mutation,異步 dispatch action。
核心原則
- 應(yīng)用層級(jí)的狀態(tài)應(yīng)該集中到單個(gè) store 對象中。
- 提交 mutation 是更改狀態(tài)的唯一方法,并且這個(gè)過程是同步的。
- 異步邏輯都應(yīng)該封裝到 action 里面。
記住這些原則,開始動(dòng)手把 Vuex 集成到 Vue 項(xiàng)目中吧,至于一些更多的概念,都是些錦上添花的東西。
Vue 應(yīng)用使用 Vuex
Vuex 的常用 api 上面都涉及到了,使用思路就是
- 定義狀態(tài):通過
new Vuex.Store({})創(chuàng)建一個(gè) store 實(shí)例,定義 state,getters,actions,mutations。 - 改變狀態(tài):使用
store.commit提交mutation,使用store.dispatch分發(fā)actions。
現(xiàn)在狀態(tài)管理的部分已經(jīng)全部由 store 實(shí)例去管理了,如何在 Vue 組件中使用 store,非常簡單,一點(diǎn)也不神奇。
在store.js文件里我們創(chuàng)建了 store 實(shí)例并將其導(dǎo)出了(export),按照 js 模塊化的知識(shí),我們只要在需要的地方導(dǎo)入它就可以直接使用了。
組件引入 store
可以在需要的組件里直接引入,就像引入一個(gè)普通的 js 一樣。
import store from "path/to/store.js";
組件中引入了 store,就可以直接通過store.state引用 state,以及直接使用 store 實(shí)例簡潔的 api,真的就是這么簡單。
store.state.count = 2; // 直接修改,并不建議
store.commit(); // 在 store 中調(diào)用 mutation
store.dispatch("increment"); // 在 store 中分發(fā) action
我們往往需要在 Vue 組件的template中展示狀態(tài),由于 Vuex 的狀態(tài)存儲(chǔ)是響應(yīng)式的,從 store 實(shí)例中讀取狀態(tài)最簡單的方法就是在計(jì)算屬性中返回某個(gè)狀態(tài):
import store from "path/to/store.js";
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count() {
return store.state.count;
}
}
};
每當(dāng) store.state.count 變化的時(shí)候, 都會(huì)重新求取計(jì)算屬性,并且觸發(fā)更新相關(guān)聯(lián)的 DOM。
其實(shí)到現(xiàn)在為止,對于在 Vue 中使用 Vuex 就已經(jīng)足夠了。下面的東西其實(shí)可以不用看了,但 Vuex 還是提供了一些方便我們使用的方式,錦上添花。
組件中引入的方式,缺點(diǎn):這種模式導(dǎo)致組件依賴全局狀態(tài)單例。在模塊化的構(gòu)建系統(tǒng)中,在每個(gè)需要使用 state 的組件中需要頻繁地導(dǎo)入。
全局引入 store
Vuex 通過 store 選項(xiàng),提供了一種機(jī)制將狀態(tài)從根組件“注入”到每一個(gè)子組件中:
// app.js
import Vue from "vue";
import store from "path/to/store.js";
const app = new Vue({
store // 把 store 對象提供給 “store” 選項(xiàng),這可以把 store 的實(shí)例注入所有的子組件
});
通過在根實(shí)例中注冊 store 選項(xiàng),該 store 實(shí)例會(huì)注入到根組件下的所有子組件中,且子組件能通過 this.$store 訪問到。這樣我們就不用每個(gè)組件單獨(dú)引入了。
this.$store.state.count = 2; // 直接修改,并不建議
this.$store.commit("", {}); // 直接在組件中提交 mutation
this.$store.dispatch("increment"); // 在組件中分發(fā) action
組件綁定的輔助函數(shù)
- mapState
- mapGetters
- mapMutations
- mapActions
既然是輔助,就不是必須的東西,輔助函數(shù)可以方便的把 Vuex 中的 state,getter,mutation,action 映射到組件,方便調(diào)用。
更多概念
Getter
如果你很喜歡 Vue 提供的計(jì)算屬性(computed),Vuex 允許在 store 中定義“getter”(可以認(rèn)為是 store 的計(jì)算屬性)。
但對狀態(tài)管理來說,Getter 其實(shí)并不是必須的,如果你需要的話,可以查看文檔使用。
Module
模塊化并不是狀態(tài)管理的概念,也不是必須的,但如果應(yīng)用十分復(fù)雜,將 store 分割成模塊(module)會(huì)是一個(gè)必經(jīng)之路,Vuex 提供了模塊化選項(xiàng),需要可查看文檔。