非父子關係的組件如何進行通訊?(Event Bus)
bus.jsvue
import Vue from 'vue'; export default new Vue();
foo.vuees6
import bus from './bus.js'; export default { methods: { changeBroData() { bus.$emit('changeBarData'); } } }
bar.vuevuex
import bus from './bus.js'; export default { created() { bus.$on('changeBarData',() => { this.count++; }); } }
查看效果npm
可是當咱們須要修改這個操做的時候,咱們須要動3個地方,假若項目小的話還倒好說,可是對於大項目組件間交互不少的概況,Event Bus就會表現的很吃力。Vuex的出現就是爲了解決這一情況。app
安裝:
script標籤引入https://unpkg.com/vuex
NPMnpm install vuex --save-dev
異步
import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex);
每個 Vuex 應用的核心就是 store(倉庫)。「store」基本上就是一個容器,它包含着你的應用中大部分的狀態 (state)。Vuex 和單純的全局對象有如下兩點不一樣:函數
簡單的Storeui
const store = new Vuex.Store({ state: { count: 0 }, mutations: { increase(state) { state.count++; } } }); store.commit('increase'); console.log(store.state.count); // 1
從上文咱們知道Vuex是響應式的,咱們如何在Vue實例中使用Vuex中的實例呢,天然離不開計算屬性computed了。this
//倉庫 const store = new Vuex.Store({ state: { count: 1 } }); //foo組件 const foo = { template: ` <div> {{ count }} <div> `, computed: { count: () => store.state.count } };
這時候問題就來了,若是這樣進行引入store的話,組件就會以來全局狀態單例。
Vuex 經過 store 選項,提供了一種機制將狀態從根組件「注入」到每個子組件中(需調用 Vue.use(Vuex)):
//掛載App new Vue({ el: '#app', //注入store選項 store, render: h => h(App) }); //foo組件 const foo = { template: ` <div> {{ count }} <div> `, computed: { count() { // 不能使用箭頭函數,否則this就不是Vue實例了 // 經過this.$store獲取到store實例 return this.$store.state.count } } };
若是有不少狀態須要映射,咱們豈不是要寫好多代碼,這時候須要用到mapState輔助函數,使用 mapState 輔助函數幫助咱們生成計算屬性。
輔助函數 實例1
const foo = { template: ` <div> count: {{ count }} countAlias: {{ countAlias }} countPlusLocalCount: {{ countPlusLocalCount }} <div> `, data() { return { localCount: 2 }; }, computed: Vuex.mapState({ //只處理倉庫中的count count: state => state.count*2, //映射 countAlias: 'count', //須要用到Vue實例的話不能使用箭頭函數否則this沒法獲取到Vue實例 countPlusLocalCount(state) { return state.count + this.localCount; } }) };
固然當映射名與state中屬性名相同時,能夠經過mapState(['count'])
這種形式書寫。
由於組件自己也有許多計算屬性直接使用mapState的話沒法擴充computed了,這時候咱們可使用ES新屬性: 對象展開運算符
computed: { localComputed() { return this.count; }, ...Vuex.mapState({ //只處理倉庫中的count count: state => state.count*2, //映射 countAlias: 'count', //須要用到Vue實例的話不能使用箭頭函數否則this沒法獲取到Vue實例 countPlusLocalCount(state) { return state.count + this.localCount; } }) }
ATT:使用 Vuex 並不意味着你須要將全部的狀態放入 Vuex。雖然將全部的狀態放到 Vuex 會使狀態變化更顯式和易調試,但也會使代碼變得冗長和不直觀。若是有些狀態嚴格屬於單個組件,最好仍是做爲組件的局部狀態。你應該根據你的應用開發須要進行權衡和肯定。
有時候咱們須要過濾/處理state中的屬性值,就像Vue實例中咱們須要處理data數據同樣,Vue有computed。Vuex一樣提供給咱們一個屬性getter,getter就是Vuex的「計算屬性」。
Getter接收state做爲第一個參數
//倉庫 const store = new Vuex.Store({ state: { users: [{ name: 'jason', id: 1, female: false }, { name: 'molly', id: 2, female: true }, { name: 'steven', id: 3, female: false }] }, getters: { // 過濾全部屬性中female是true的對象 getFemaleUsers: state => state.users.filter(user => user.female) } }); console.log(store.getters.getFemaleUsers); //[{ "name": "molly", "id": 2, "female": true }]
固然Getter一樣有輔助函數 mapGetters將 store 中的 getter 映射到局部計算屬性
直接上對象展開運算符了
輔助函數 實例1
//foo組件 const foo = { template: ` <div> femaleList: {{ femaleList }} <div> `, computed: { // 屬性名相同的再也不闡述 ...Vuex.mapGetters({ femaleList: 'getFemaleUsers' }) } };
更改 Vuex 的 store 中的狀態的惟一方法是提交 mutation
提交時額外的參數被稱爲載荷
普通風格提交方式
store.commit('mutationName', { params: '參數' });
對象風格提交方式
store.commit({ type: 'mutationName', params: '參數' });
注意點:
Vue.set(obj, 'newVal', 100)
obj = {...obj, newVal: 100};
Mutations的事件類型儘可能使用常量,並用一個文件進行維護。方便協同開發以及維護。
// 存儲事件名mutations-types.js export const MUTATIONS_GETDATA = 'MUTATIONS_GETDATA'; export const MUTATIONS_SUCCESS = 'MUTATIONS_SUCCESS';
import { MUTATIONS_GETDATA ,MUTATIONS_SUCCESS } from 'path/mutations-types.js'; const store = new Vuex.Store({ mutations: { [MUTATIONS_GETDATA]() { // todo }, [MUTATIONS_SUCCESS]() { // todo } } });
在組件中提交Mutation$store.commit('xxx','yyy')
固然也有對應的輔助函數幫助咱們映射到對應的methods方法中
import { mapMutations } from 'vuex'; export default { methods: { ...mapMutations([ 'event1', 'event2' ]), ...mapMutations({ eventAlias: 'event3' //將this.eventAlias()映射爲this.$store.commit('event3') }) } };
Action與Mutation的不一樣在於Action不直接修改狀態只是作commit一個mutation、而後就是能夠作異步操做。
簡單的Action
const INCREASE_COUNT = 'INCREASE_COUNT'; const store = new Vuex.Store({ state: { count: 0 }, mutations: { [INCREASE_COUNT](state) { state.count++; } }, actions: { add({ commit }) { commit(INCREASE_COUNT); } } });
看到這咱們或許覺得Actions的做用與Mutations不同麼,對到如今爲止做用是同樣的,可是Actions能夠執行異步操做
const INCREASE_COUNT = 'INCREASE_COUNT'; const store = new Vuex.Store({ state: { count: 0 }, mutations: { [INCREASE_COUNT](state) { state.count++; } }, actions: { add({ commit }) { setTimeout(() => { commit(INCREASE_COUNT); }, 1000); } } });
Action的分發也有兩種方式
普通風格分發方式
store.dispatch('actionName', { params: '參數' });
對象風格分發方式
store.dispatch({ type: 'actionName', params: '參數' });
輔助函數mapActions用法與mapMutations無異
代碼連接
methods: { ...mapActions({ add: 'add' }) }
組合Action
actions: { actionA ({ commit }) { return new Promise((resolve, reject) => { setTimeout(() => { commit('someMutation') resolve() }, 1000) }) }, // ... actionB ({ dispatch, commit }) { return dispatch('actionA').then(() => { commit('someOtherMutation') }) } } store.dispatch('actionA').then(() => { // ... }) 或者 store.dispatch('actionB');
若是項目龐大的話咱們須要維護不少狀態,這時候store會變得很是龐大,咱們就須要store分割成不少模塊(Module),每一個模塊一樣擁有本身的state,getters,mutations,actions以及modules
const moduleA = { state: { a: 'A' } }; const moduleB = { state: { a: 'B' } }; const store = new Vuex.Store({ modules: { moduleA, moduleB } }); console.log(store.state.moduleA.a); //A console.log(store.state.moduleB.a); //B
模塊內部的mutation與getter接收的第一個參數都是局部狀態對象
例如 const moduleA = { state: { price: 50, count: 2 }, getters: { totalPrice: state => state.price*state.count }, mutations: { decreaseCount(state) { state.count--; } } };
模塊內部的getter,根節點的狀態會在第三個參數展現出來。
注:根節點的狀態就是指的store.state
const moduleB = { state: { count: 2 }, getters: { sum: (state, getters, rootState) => state.count+rootState.count } }; const store = new Vuex.Store({ state: { count: 1 }, modules: { moduleB } }); console.log(store.getters.sum);// 3
模塊內部的action,局部狀態:context.state
根部狀態:context.rootState
const moduleC = { state: { count: 2 }, mutations: { increaseCount(state) { state.count++; } }, actions: { addCount({ commit, state, rootState }) { let sum = state.count + rootState.count; if(sum % 3 === 0) { commit('increaseCount'); } } } }; const store = new Vuex.Store({ state: { count: 1 }, modules: { moduleC } }); console.log(store.state.moduleC.count);// 2 store.dispatch('addCount'); console.log(store.state.moduleC.count);// 3
命名空間
默認狀況下,模塊內部的 action
、mutation
和 getter
是註冊在全局命名空間的——這樣使得多個模塊可以對同一 mutation
或 action
做出響應。
若是但願你的模塊具備更高的封裝度和複用性,你能夠經過添加 namespaced: true
的方式使其成爲命名空間模塊。當模塊被註冊後,它的全部 getter
、action
及 mutation
都會自動根據模塊註冊的路徑調整命名。(摘自官網)
注1:特別提出:模塊內的state是嵌套的,namespaced
屬性不會對其產生絲毫影響。
注2:沒加namespaced: true
的模塊會繼承父模塊的命名空間
代碼連接
const store = new Vuex.Store({ modules: { moduleA: { namespaced: true, getters: { count: () => 0 // store.getters['moduleA/count'] }, modules: { moduleB: { getters: { count1: () => 1 //繼承了父模塊的命名空間 store.getters['moduleA/count1'] } }, moduleC: { namespaced: true, getters: { count2: () => 2 //繼承了父模塊的命名空間 store.getters['moduleA/moduleC/count1'] } } } } } }); console.log(store.getters['moduleA/count']);// 0 console.log(store.getters['moduleA/count1']);// 1 console.log(store.getters['moduleA/moduleC/count2']);// 2
在命名空間模塊內部訪問全局內容
模塊內部但願使用全局state
與全局getter
rootState
與rootGetters
會做爲getter
第三個第四個參數傳入
如
someGetter: (state, getters, rootState, rootGetters) => { //todo }
const store = new Vuex.Store({ getters: { someOtherGetter: state => 'ROOT' }, modules: { moduleA: { namespaced: true, getters: { someGetter(state,getters,rootState,rootGetters) { return getters.someOtherGetter; // INSIDE // return rootGetters.someOtherGetter; // ROOT }, someOtherGetter: state => "INSIDE" } } } }); console.log(store.getters['moduleA/someGetter']);
也會經過context.rootState
與context.rootGetts
傳入
如
someAction:(context) => { //todo } 或者 someAction:({ commit, dispatch, rootState, rootGetters }) => { //todo }
模塊內部但願使用全局mutation
與全局action
,只須要在執行分發或者提交的時候在第三個參數位置傳入{ root: true }
如
dispatch('someOtherAction', null, {root: true}); commit('someOtherMutation', null, {root: true});
const store = new Vuex.Store({ getters: { someOtherGetter: state => 'ROOT' }, actions: { someOtherAction() { console.log('ROOT_ACTION'); } }, modules: { moduleA: { namespaced: true, getters: { someGetter(state,getters,rootState,rootGetters) { return getters.someOtherGetter; // INSIDE // return rootGetters.someOtherGetter; // ROOT }, someOtherGetter: state => "INSIDE" }, actions: { someAction({ dispatch, getters, rootGetters }) { console.log(getters.someOtherGetter);//INSIDE console.log(rootGetters.someOtherGetter);//ROOT dispatch('someOtherAction');//INSIDE_ACTION dispatch('someOtherAction', null, {root: true});//ROOT_ACTION }, someOtherAction() { console.log('INSIDE_ACTION'); } } } } }); console.log(store.getters['moduleA/someGetter']); store.dispatch('moduleA/someAction');
當咱們將store裏的狀態映射到組件的時候,會出現下面的問題
computed: { ...mapState({ b1:state => state.moduleA.moduleB.b1, b2:state => state.moduleA.moduleB.b2 }) }, methods: { ...mapActions({ 'moduleA/moduleB/action1', 'moduleA/moduleB/action2' }) } 是否是比較繁瑣,代碼太過冗餘。
固然mapState
、mapGetters
、mapMutations
、mapActions
這些輔助函數均可以將空間名稱字符串做爲第一個參數傳遞,這樣上面的例子能夠簡化成
computed: { ...mapState('moduleA/moduleB', { b1:state => state.b1, b2:state => state.b2 }) }, methods: { ...mapActions('moduleA/moduleB', { 'action1', 'action2' }) }
固然還有一個方法,使用Vuex
提供的createNamespacedHelpers
建立基於某個命名空間函數,它返回一個對象,對象裏有新的綁定在給定命名空間值上的組件綁定輔助函數。
import { createNamespacedHelpers } from 'vuex'; const { mapState , mapActions } = createNamespacedHelpers('moduleA/moduleB'); export default { computed: { ...mapState({ b1:state => state.b1, b2:state => state.b2 }) }, methods: { ...mapActions({ 'action1', 'action2' }) } }
模塊重用
有時咱們可能須要建立一個模塊的多個實例:
若是咱們使用一個純對象來聲明模塊的狀態,那麼這個狀態對象會經過引用被共享,致使狀態對象被修改時 store 或模塊間數據互相污染的問題。
實際上這和 Vue 組件內的 data 是一樣的問題。所以解決辦法也是相同的——使用一個函數來聲明模塊狀態
const MyReusableModule = { state () { return { foo: 'bar' } }, // mutation, action 和 getter 等等... }