在單頁應用的開發中,Vue全家桶的Vuex和React全家桶的Redux都是統一的狀態管理工具/模式。它們是全局的,簡單來講,就是在store中定義一個數據,就能夠在整個項目中獲取、修改,而且修改會獲得全局的響應變動;html
這些狀態管理工具適用於中大型項目,在小型項目會使得項目變得繁雜;vue
先上一張Vuex官網的架構圖:vuex
Vuex使用的是單一狀態樹,即state是一個對象,包含了全部定義的數據。redux
在單一的模塊module.js中,咱們能夠這樣定義一個數據:api
const person = {
state: {
name: "tom",
age: 20
}
};
export default person;
複製代碼
而後由index.js統一引入,實例化一個新的store:緩存
import Vue from "vue";
import Vuex from "vuex";
import person from "@/store/modules/module";
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
person
}
});
export default store;
複製代碼
最後在main.js中注入全局:架構
import Vue from "vue";
import store from "@/store";
new Vue({
store
});
複製代碼
咱們須要person的信息時,使用this.$store.state.person
便可獲取。異步
單當統一組件屢次須要person、person1的信息時,這時候使用mapState函數就很很方便。其中mapState中的函數接收的參數是該模塊局部狀態對象state。async
// vuex自帶的mapState api
import { mapState } from "vuex";
export default {
// ...
computed: {
getTest() { return "test" },
mapState({
// 箭頭函數可以使代碼更簡練
person: state => state.person,
// 傳字符串參數 'person' 等同於 `state => state.person`
personAlias: 'person',
// 爲了可以使用 `this` 獲取局部狀態,必須使用常規函數
personName(state) {
return state.person.name + this.localPerson.name
}
})
// 對象展開運算符更方便
// ...mapState({
// person: state => state.person
// })
// 對象展開運算符加名稱縮寫究極方便
// ...mapState([
// "person"
// ])
}
}
複製代碼
有時候咱們不單單須要state的值,多個組件須要這個值的衍生值,如將數值由人民幣換算成美圓,這時候就是getter出場的時候了,這一點它有點相似compute,getters返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變纔會被從新計算。函數
getter接受 state
做爲其第一個參數,接受 getters
做爲其第二參數,對於模塊內部的getter來講,接受rootState
做爲第三個參數,爲根節點的state。
const person = {
state: {
name: "tom",
age: 20,
money: 100
},
getters: {
getAge(state) {
return state.age / 10;
}
convert(state, getters, rootState) => {
// 第二個參數getters返回該store.getters對象
return state.money * 100 * getters.getAge;
},
// getter也能夠返回函數,以達到傳參的目的
// 1:人民幣轉美圓 0:反之
convertByRate: (state) => (type) => {
const rate = type === 1 ? 6.8925 : 0.1451;
return state.money * rate;
}
}
};
複製代碼
// getter傳參
// 轉美圓
this.$store.getters.convertByRate(1);
複製代碼
用法和mapState相同,將 store 中的 getter 映射到局部計算屬性
mapGetters中方法接受一個參數:context對象,等同於store實例
// 組件中:
import { mapGetters } from "vuex";
// ...
computed: ...mapGetters({
convert: context => context.getters.convert
});
// computed: ...mapGetters([
// "convert"
// ]);
複製代碼
Vuex中,在組件中直接改變state的值是不行的,惟一改變狀態的方法是提交 mutation 。每一個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler),這是官方的說法,我以爲其實就是事件名和事件本體啦,這樣更好理解。事件第一個參數是state,第二個參數是載荷(payload),可選,通常狀況下是一個對象。(載荷payload這個名字也是頗有逼格,redux中也是這樣叫的,vuex參考了redux)
仍是以前的例子:
const person = {
state: {
name: "tom",
age: 20,
money: 100
},
getters: {
getAge(state) {
return state.age / 10;
}
},
mutations: {
setName(state, payload) {
state.name = payload.name;
}
// 或者簡單點,不傳對象直接傳值
// setName(state, val) {
// state.name = val;
// }
}
};
複製代碼
那麼怎麼調用這個mutation來改變state的值呢?
this.$store.commit("setName", { name: "rachael" });
// 對象的形式調用
this.$store.commit({
type: "setName",
name: "rachael"
});
複製代碼
同mapState 與 mapGetters用法,咱們將mutation的方法映射到本地組件中;
import { mapMutations } form "vuex";
export default {
methods: {
...mapMutations([
// 將this.setName(payload) 映射爲 this.$store.commit("setName", payload)
"setName"
])
}
}
複製代碼
在mutations中只支持同步事件,須要處理一些http請求等異步事件時就須要actions了
在action中,不是直接改變狀態的值,而是提交mutation來改變。action中能夠包含任意的異步操做。
仍是以前的例子:
const person = {
state: {
name: "tom",
age: 20,
money: 100
},
getters: {
getAge(state) {
return state.age / 10;
}
},
mutations: {
setName(state, payload) {
state.name = payload.name;
}
},
actions: {
setName(context) {
// context參數與 store 實例具備相同方法和屬性,當不等同於store實例,其還有rootState屬性,即根節點state
setTimeout(() => {
context.commit("setName", { name: "rachael" });
}, 1000)
},
// 參數解構簡寫
// setName({ commit, rootState }) {
// rootState爲該模塊根節點state值
// commit("setName", { name: "rachael" });
// }
}
};
複製代碼
this.$store.dispatch('setName', { name: "rachael" });
複製代碼
一樣也有mapActions函數,用法和mapMutations相同;
當一個action函數須要調用其餘異步的action1時,將action1返回一個Promise並結合async / await
調用便可。
默認狀況下,模塊內部的 action、mutation 和 getter 是註冊在全局命名空間的。在調用時,直接全局調用:
this.$store.getters.getAge();
this.$store.commit("setName", { name: "rachael" });
this.$store.dispatch("setName", { name: "rachael" });
複製代碼
註冊爲模塊命名空間的就不討論了,通常用默認的全局就能夠了,模塊命名空間會麻煩不少。
使用常量代替方法名,再把這些常量放在統一的一個文件:types.js中,有助於咱們維護和理解。
types.js:
export const SET_NAME = "SET_NAME";
export const GET_NAME = "GET_NAME";
複製代碼
在module.js中就能夠改成:
import {
SET_NAME,
GET_NAME
} form "@/store/types";
const person = {
state: {
name: "tom",
age: 20,
money: 100
},
getters: {
[GET_NAME](state) {
return state.age / 10;
}
},
mutations: {
[SET_NAME](state, payload) {
state.name = payload.name;
}
},
actions: {
[GET_NAME]({ commit, rootState }) {
// context參數與 store 實例具備相同方法和屬性,當不等同於store實例,其還有rootState屬性,即根節點state
setTimeout(() => {
commit("setName", { name: "rachael" });
}, 1000)
}
}
};
複製代碼
this.$store.getters.[GET_NAME]();
this.$store.commit("SET_NAME", { name: "rachael" });
this.$store.dispatch("SET_NAME", { name: "rachael" });
複製代碼
再回到這張圖,是否是就好理解了~
首發於本人的博客:柴犬工做室