Vuex-一個專爲 Vue.js 應用程序開發的狀態管理模式

爲何會出現Vuex

非父子關係的組件如何進行通訊?(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

Vuex介紹

安裝:
script標籤引入
https://unpkg.com/vuex
NPM
npm install vuex --save-dev異步

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);

每個 Vuex 應用的核心就是 store(倉庫)。「store」基本上就是一個容器,它包含着你的應用中大部分的狀態 (state)。Vuex 和單純的全局對象有如下兩點不一樣:函數

  1. Vuex的狀態存儲是響應式的。當Vuex的狀態屬性發生變化時,相應的組件也會更新。
  2. 沒法直接修改Vuex的狀態。只能經過顯式的提交(commit)。

簡單的Storeui

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increase(state) {
      state.count++;
    }
  }
});
store.commit('increase');
console.log(store.state.count); // 1

State

從上文咱們知道Vuex是響應式的,咱們如何在Vue實例中使用Vuex中的實例呢,天然離不開計算屬性computed了。this

在 Vue 組件中得到 Vuex 狀態spa

//倉庫
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 會使狀態變化更顯式和易調試,但也會使代碼變得冗長和不直觀。若是有些狀態嚴格屬於單個組件,最好仍是做爲組件的局部狀態。你應該根據你的應用開發須要進行權衡和肯定。

Getter

有時候咱們須要過濾/處理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'
    })
  }
};

Mutation

更改 Vuex 的 store 中的狀態的惟一方法是提交 mutation
提交時額外的參數被稱爲載荷
普通風格提交方式

store.commit('mutationName', {
    params: '參數'
});

對象風格提交方式

store.commit({
    type: 'mutationName',
    params: '參數'
});

注意點:

  1. 設置對象新屬性時
    方法1:Vue.set(obj, 'newVal', 100)
    方法2:obj = {...obj, newVal: 100};
  2. 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
            }
        }
    });
  3. Mutations中的函數必須都是同步函數,異步函數都要寫在Actions中。

在組件中提交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

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');

Module

若是項目龐大的話咱們須要維護不少狀態,這時候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
  1. 模塊內部的mutationgetter接收的第一個參數都是局部狀態對象

    例如
    const moduleA = {
        state: {
            price: 50,
            count: 2
        },
        getters: {
            totalPrice: state => state.price*state.count
        },
        mutations: {
            decreaseCount(state) {
                state.count--;
            }
        }
    };
  2. 模塊內部的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
  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

命名空間
默認狀況下,模塊內部的 actionmutationgetter 是註冊在全局命名空間的——這樣使得多個模塊可以對同一 mutationaction 做出響應。
若是但願你的模塊具備更高的封裝度和複用性,你能夠經過添加 namespaced: true 的方式使其成爲命名空間模塊。當模塊被註冊後,它的全部 getteractionmutation 都會自動根據模塊註冊的路徑調整命名。(摘自官網)
注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

  1. rootStaterootGetters會做爲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']);
  2. 也會經過context.rootStatecontext.rootGetts傳入

    someAction:(context) => {
        //todo
    }
    或者
    someAction:({ commit, dispatch, rootState, rootGetters }) => {
        //todo
    }

模塊內部但願使用全局mutation與全局action,只須要在執行分發或者提交的時候在第三個參數位置傳入{ root: true }

dispatch('someOtherAction', null, {root: true});
commit('someOtherMutation', null, {root: true});

栗子(沒有寫mutation與state的能夠自行嘗試)

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'
    })
}
是否是比較繁瑣,代碼太過冗餘。

固然mapStatemapGettersmapMutationsmapActions這些輔助函數均可以將空間名稱字符串做爲第一個參數傳遞,這樣上面的例子能夠簡化成

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'
        })
    }
}

模塊重用
有時咱們可能須要建立一個模塊的多個實例:

  1. 建立多個 store,他們公用同一個模塊
  2. 在一個 store 中屢次註冊同一個模塊

若是咱們使用一個純對象來聲明模塊的狀態,那麼這個狀態對象會經過引用被共享,致使狀態對象被修改時 store 或模塊間數據互相污染的問題。

實際上這和 Vue 組件內的 data 是一樣的問題。所以解決辦法也是相同的——使用一個函數來聲明模塊狀態

const MyReusableModule = {
  state () {
    return {
      foo: 'bar'
    }
  },
  // mutation, action 和 getter 等等...
}
相關文章
相關標籤/搜索