本文對 Vuex 官方文件重新組織編排,希望正在學習 Vue 的同學們,在閱讀後可快速使用 Vuex。
開始使用 Vuex,把狀態拿到應用外部管理,Vuex管這個管理狀態的玩意叫 Store,一個完全獨立的應用,他只負責狀態管理。嘗試把 Vuex 應用和 Vue 應用劃清界限,
- 一個 Vuex 應用,做狀態管理,可以理解是 Model 層
- 一個 Vue 應用,僅負責資料展示,純純的 View 層
Vuex 應用
所謂狀態管理,無非就是定義狀態,修改狀態。
定義 state
在 Vuex 裡定義狀態,我們需要 new 一個 Store 出來,每一個 Vuex 應用的核心就是 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;
複製程式碼
以上程式碼建立了一個 store,store.state
裡定義了狀態。與在 Vue 裡定義 data 沒有任何區別,
下面修改狀態。
直接修改
如果你想快點用上 Vuex,你可以在元件裡直接修改 store 裡的 state,(直接修改的意思就是,用點操作修改)
state.count = 2;
複製程式碼
雖然這樣可以正常工作,但在嚴格模式下會報錯,更改 store 中的狀態的唯一方法應該是提交 mutation。
嚴格模式
開啟嚴格模式,僅需在建立 store 的時候傳入 strict: true
:
const store = new Vuex.Store({
strict: true
});
複製程式碼
在嚴格模式下,無論何時發生了狀態變更且不是由 mutation 函式引起的,將會丟擲錯誤。
mutation
更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。
在Vuex.Store的構造器選項中,有一個 mutation 選項,這個選項就像是事件註冊:key 是一個字串表示 mutation 的型別(type),value 是一個回撥函式(handler),
這個回撥函式就是我們實際進行狀態更改的地方,它有兩個入參,第一個引數是 state,就是 store 裡的 state;第二個引數是 Payload,這是提交 mutation 時候額外傳入的引數。
定義 mutation
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment(state) {
state.count++; // 變更狀態
}
}
});
複製程式碼
提交 mutation
上面定義了 mutation,要喚醒一個 mutation handler,唯一的介面是store.commit
,如果你熟悉事件監聽,commit 就類似於 Vue 的$emit
,jquery 的trigger
。
可想而知,commit 方法傳參必須至少有一個能區分 mutation 的唯一標識,這個標識就是 type
,
commit 引數是物件
當 commit 的引數是一個物件的時候,物件裡必須要有 type 欄位,除了 type 欄位,你還可以新增額外任意欄位為載荷(payload)。
// 物件風格的提交方式
store.commit({
type: "increment",
amount: 10
});
複製程式碼
type 做第一引數
把 type 和 payload 分開也是個不錯的選擇,可以把 type 單獨拿出來當第一個引數,以commit(type,[payload])
的形式提交,官方稱此為以載荷形式提交。
store.commit("increment");
store.commit("increment", 10);
store.commit("increment", { count: 2 });
複製程式碼
在大多數情況下,payload 應該是一個物件,這樣可以包含多個欄位並且記錄的 mutation 會更易讀。
到這裡我們已經知道如何定義 mutation 和提交 mutation 了,commit 介面很簡單,但在哪裡使用呢?有兩個地方用
- Vue 應用的元件裡,在元件裡呼叫
store.commit
。 - 還有 store 的 action 裡。
Action
狀態管理不過就是定義 state 和修改 state,mutation 已經可以修改 state 了,為什麼還需要 action?
同步和非同步
在 Vuex 中,mutation 都是同步事務,在 mutation 中混合非同步呼叫會導致你的程式很難除錯。
例如,當你呼叫了兩個包含非同步回撥的 mutation 來改變狀態,你怎麼知道什麼時候回撥和哪個先回撥呢?這就是為什麼我們要區分這兩個概念。
Action 確實和 mutation 很類似,不同在於:
- Action 提交的是 mutation,而不是直接變更狀態。
- Action 可以包含任意非同步操作。
註冊 action:
註冊 action 就跟定義 mutation 一樣,除了 handler 的入參不同。
Action 函式的入參是一個與 store 例項具有相同方法和屬性的 context
物件。因此可以
context.commit
提交一個 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");
}
}
});
複製程式碼
分發 Action
Action 通過 store.dispatch
方法觸發:
// 以載荷形式分發
store.dispatch("incrementAsync", {
amount: 10
});
// 以物件形式分發
store.dispatch({
type: "incrementAsync",
amount: 10
});
複製程式碼
組合 Action
store.dispatch
可以處理被觸發的 action 的處理函式返回的 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
時候,v-model
會試圖直接修改 state,而 Vuex 在嚴格模式下是不允許直接修改 state 的。
很容易解決,只要我們把修改 state 的行為按 Vuex 的要求以commit mutation 方式修改即可。
有兩種方式。
用“Vuex 的思維”解決
拋棄v-model
指令,自己去實現v-model
雙向繫結,非常簡單,
input
標籤的value
屬性繫結對應從 store 中對映來的計算屬性,- 監聽 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 函式:
mutations: {
updateMessage (state, message) {
state.obj.message = message
}
}
複製程式碼
用 Vue 計算屬性解決
如果堅持使用v-model
,可以在 Vue 裡對v-model
繫結的計算屬性設定 set 行為。
<input v-model="message" />
複製程式碼
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
複製程式碼
使用 Vuex 進行狀態管理,到此結束。
從 flux 架構來看,三個核心,State,Mutation,Action,已經足夠了,總結一下其實很簡單,同步 commit mutation,非同步 dispatch action。
核心原則
- 應用層級的狀態應該集中到單個 store 物件中。
- 提交 mutation 是更改狀態的唯一方法,並且這個過程是同步的。
- 非同步邏輯都應該封裝到 action 裡面。
記住這些原則,開始動手把 Vuex 整合到 Vue 專案中吧,至於一些更多的概念,都是些錦上添花的東西。
Vue 應用使用 Vuex
Vuex 的常用 api 上面都涉及到了,使用思路就是
- 定義狀態:通過
new Vuex.Store({})
建立一個 store 例項,定義 state,getters,actions,mutations。 - 改變狀態:使用
store.commit
提交mutation
,使用store.dispatch
分發actions
。
現在狀態管理的部分已經全部由 store 例項去管理了,如何在 Vue 元件中使用 store,非常簡單,一點也不神奇。
在store.js
檔案裡我們建立了 store 例項並將其匯出了(export),按照 js 模組化的知識,我們只要在需要的地方匯入它就可以直接使用了。
元件引入 store
可以在需要的元件裡直接引入,就像引入一個普通的 js 一樣。
import store from "path/to/store.js";
複製程式碼
元件中引入了 store,就可以直接通過store.state
引用 state,以及直接使用 store 例項簡潔的 api,真的就是這麼簡單。
store.state.count = 2; // 直接修改,並不建議
store.commit(); // 在 store 中呼叫 mutation
store.dispatch("increment"); // 在 store 中分發 action
複製程式碼
我們往往需要在 Vue 元件的template
中展示狀態,由於 Vuex 的狀態儲存是響應式的,從 store 例項中讀取狀態最簡單的方法就是在計算屬性中返回某個狀態:
import store from "path/to/store.js";
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count() {
return store.state.count;
}
}
};
複製程式碼
每當 store.state.count
變化的時候, 都會重新求取計算屬性,並且觸發更新相關聯的 DOM。
其實到現在為止,對於在 Vue 中使用 Vuex 就已經足夠了。下面的東西其實可以不用看了,但 Vuex 還是提供了一些方便我們使用的方式,錦上添花。
元件中引入的方式,缺點:這種模式導致元件依賴全域性狀態單例。在模組化的構建系統中,在每個需要使用 state 的元件中需要頻繁地匯入。
全域性引入 store
Vuex 通過 store 選項,提供了一種機制將狀態從根元件“注入”到每一個子元件中:
// app.js
import Vue from "vue";
import store from "path/to/store.js";
const app = new Vue({
store // 把 store 物件提供給 “store” 選項,這可以把 store 的例項注入所有的子元件
});
複製程式碼
通過在根例項中註冊 store 選項,該 store 例項會注入到根元件下的所有子元件中,且子元件能通過 this.$store
訪問到。這樣我們就不用每個元件單獨引入了。
this.$store.state.count = 2; // 直接修改,並不建議
this.$store.commit("", {}); // 直接在元件中提交 mutation
this.$store.dispatch("increment"); // 在元件中分發 action
複製程式碼
元件繫結的輔助函式
- mapState
- mapGetters
- mapMutations
- mapActions
既然是輔助,就不是必須的東西,輔助函式可以方便的把 Vuex 中的 state,getter,mutation,action 對映到元件,方便呼叫。
更多概念
Getter
如果你很喜歡 Vue 提供的計算屬性(computed),Vuex 允許在 store 中定義“getter”(可以認為是 store 的計算屬性)。
但對狀態管理來說,Getter 其實並不是必須的,如果你需要的話,可以檢視文件使用。
Module
模組化並不是狀態管理的概念,也不是必須的,但如果應用十分複雜,將 store 分割成模組(module)會是一個必經之路,Vuex 提供了模組化選項,需要可檢視文件。
原文連結:歡迎github來顆star github.com/liangzai92/…