層次和約束:專案中使用vuex的3條優化方案

凌霄光發表於2018-10-22

問題描述

使用vuex的store的過程中,發現了一些不是很優雅的地方:

  1. store層module太多,找state、getter、mutation、action對應的module比較慢。

層次和約束:專案中使用vuex的3條優化方案

  1. 元件裡面mapGetters、mapActions、mapMutations過多,分不清getter、action、mutation屬於哪個module,比較混亂。

層次和約束:專案中使用vuex的3條優化方案

  1. 沒有使用mutation type 和action type的列舉常量來約束action type和mutation type取值,字串方式容易出錯。(如上圖)

解決方案

針對這3個問題,制定了3條重構方案。

1. module聚類分層

按照聚類分層思想,當業務複雜後,需要通過一定的聚類特徵對扁平化的結構進行分層。

這裡按照資料的用途分了page、components、domain、other這四類,page儲存頁面元件的資料,components儲存基礎元件的資料,domain儲存實體的資料,other儲存其他全域性資料。

之前的modules

層次和約束:專案中使用vuex的3條優化方案

之後的modules

層次和約束:專案中使用vuex的3條優化方案
目前還沒有儲存實體資料的module,所以暫時為空

2.module新增namespace

store劃分module是因為不同的資料有不同的歸屬。

如果想要每個module都能響應全域性action的話,不需要加namespace,但是我們並沒有沒有一個action對應多個module的action handler的情況。反而因為沒有加namespace,導致元件裡的多個module的getter、action、mutation都扁平的堆在一起,結構混亂、不清晰。

    ...mapMutations([
      changeisIceTree: 'changeisIceTree',
      changeIceTreeStatus: 'changeIceTreeStatus',
      showToast: 'showToast',
      changeremainingfronzeTime: 'changeremainingfronzeTime',
      decreaseremainingfronzeTime: 'decreaseremainingfronzeTime',
      changeiceTreeFadeout: 'changeiceTreeFadeout',
      changeiceTreeFadeIn: 'changeiceTreeFadeIn',
      changefrozenTimes: 'changefrozenTimes',
      changetreecurTime: 'changetreecurTime',
      changequickTreeMedal:'changequickTreeMedal',
      changequickHonorMedal:"changequickHonorMedal",
      upDatePopUpOptionStatus: 'upDatePopUpOptionStatus'
    }),
複製程式碼

一堆的mutation讓人迷惑,結構很不清晰,哪個mutation是哪個module必須去store中找。

加上namespace之後,每個mutaion屬於一個namespace,每個namespace代表一個module,在元件裡就可以輕鬆的根據namespace區分出哪個module來。

...mapGetters('aaaaa',[
   'mutation111111',
   'mutation22222',
   'mutation33333'
]);
...mapMutations('aaaaa',[
   'mutation111111',
   'mutation22222',
   'mutation33333'
]);
...mapMutations('bbbbb',[
   'mutation4444444',
   'mutation555555',
   'mutation666666',
]);
複製程式碼

這樣重構之後,元件用到再多module的action、getter、mutation也不會混亂了。

3.mutation type和action type使用列舉常量約束

mutation type和action type的名字可能會寫錯,因為沒有使用typescript,沒有型別約束,如果寫錯了,編譯時無法檢查出來,只能在執行時檢查。解決這個問題或者使用ts,或者全部的mutation type和action type從列舉常量中取。

store中的資料是模組化的,mutation type 和action type的列舉常量自然也是,但是vuex的module並不會處理這兩者,想把這些模組化的motation type和action type掛到store例項上,可以通過vuex外掛來解決。

我發現社群並沒有我需要的vuex外掛,於是我自己封裝了一個

/**
 * 生成檔案對應的模組
 * 
 * @param {*} dirPath 資料夾路徑
 */
const generateModules = (files) => {
    const modules = {}

    files.keys().forEach(key => {
        modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default
    })
    return modules;
}


/**
 * 所有file
 * 
 */
const allFiles = {
    page: require.context('../modules/page', false, /\.js$/),
    components: require.context('../modules/components', false, /\.js$/),
    domain: require.context('../modules/domain', false, /\.js$/),
    other: require.context('../modules/other', false, /\.js$/)
}

/**
 * 所有module
 * 
 */
const allModules = {
    page: generateModules(allFiles.page),
    components: generateModules(allFiles.components),
    domain: generateModules(allFiles.domain),
    other: generateModules(allFiles.other)
}


/**
 * 根據types獲取modules下的多個模組的結構化資料
 * @param {*} types module type
 * @param {*} fieldName 欄位名
 */
const getStructuredData = (types, fieldNames) => {
    const structuredData = {};
    types.forEach(type => {
        const modules = allModules[type];
        const structuredModuleData = Object.keys(modules).map(moduleName => {
            const fields = fieldNames.map(fieldName => modules[moduleName][fieldName])
            return {
                [moduleName]: Object.assign(...fields)
            }
        });
        structuredData[type]= structuredModuleData && structuredModuleData.length ? Object.assign(...structuredModuleData): {};
    })
    return structuredData
}


const enumTypePlugin = store => {

    const mutationTypeEnum = getStructuredData(['page','components','domain','other'], ['mutationTypes']);
    const actionTypeEnum = getStructuredData(['page','components','domain','other'], ['actionTypes']);

    store.mutationTypes = mutationTypeEnum;
    store.actionTypes = actionTypeEnum;
}
module.exports = enumTypePlugin;
複製程式碼

新增到vuex的plugins中

import typeEnumPlugin from './type-enum-plugin';

new Vuex.Store(
  modules,
  plugins: [typeEnumPlugin]
)
複製程式碼

module定義時匯出mutation types和action types

module.exports = {
   state,
   getters,
   mutations,
   actions,
   mutationTypes,
   actionTypes
}
複製程式碼

在元件裡面就可以使用action type和mutation type來mapAction,mapMutation

...mapActions({
  mutation1: this.$store.mutationTypes.page.aaa.mutation1,
  mutation2: this.$store.mutationTypes.page.aaa.mutation2,
  mutation3: this.$store.mutationTypes.page.aaa.mutation3
})
...mapActions({
  action1: this.$store.actionTypes.page.aaa.action1,
  action2: this.$store.actionTypes.page.aaa.action2,
  action3: this.$store.actionTypes.page.aaa.action3
})
複製程式碼

或者像下面這樣全部匯入

...mapMutations(this.$store.mutationTypes.page.aaa)
...mapActions(this.$store.actionTypes.page.aaa)
複製程式碼

這樣就避免了手寫字串可能出錯的問題。

總結

針對vuex store的module過多,元件裡無法區分出getter、action、mutation屬於哪一個module,mutation type和action type無約束這3個問題,針對性的提出了3條解決方案:

module聚類分層,分成page、components、domain、other四個資料夾存放module;

新增namespace,元件中使用mapGetters、mapActions、mapMuatations時加上namespace區分;

module定義時匯出mutation types和action types,並通過vuex的外掛掛到store上,元件中使用mapMutations和mapActions不再通過字串取對應值。

相關文章