通过单例模式设计vuex动态注册模块

1,659 阅读4分钟

背景

常规的vuex模块是放在 store/modules/ 下

  • 与业务分隔严重,很难保证维护者不会引入其他业务需求代码;
  • 并且长期以往代码量积累,后期难以维护;

vuex动态注册优势

  • vuex模块存放地点 (就近管理,存在于业务模块根目录/__store) 更加贴近对应的需求代码;
  • 可以在离开业务模块后注销相应的vuex模块,不会对其他业务模块造成影响;

基础使用

业务模块中

  • 在created中注册模块;如果需要,在beforeDestroy中注销模块;
  • 需要获得store主模块的引用
  • 需要考虑子模块是否重复注册问题
import store from '@/store'
import customModule from './__sotre'

export default {
 created() {
  store.registerModule('customModule', customModule)
 },
 beforeDestroy() {
  store.unregisterModule('customModule')
 },
}

store子模块中

建议使用函数的形式初始化state,保证如果注销后再次注册数据是干净的

// 使用函数初始化state,保证每次注册都是干净的值
function initialState() {
    return {
        testData: [],
    }
}

export default {
    namespaced: true,
    state: initialState,
}

优化思路

通过Vue.use插件形式进行封装,满足以下需求

  • 模块名校验,统一以Module结尾,保证与主模块名不会冲突
  • 检测子模块是否重复注册
  • 让 注销模块功能 跟随组件销毁自动执行,并通过可选参数取消

期望使用方式

import customModule from './__sotre'

export default {
 created() {
  this.$registerModule('customModule', customModule)
 },
}

注册实例属性

需要将store主模块的引用传入

export default {
    install(Vue, options) {
        // 注册store模块时,需要主模块的引用
        const {store} = options
        Vue.prototype.$registerModule = registerModule
    }
}

模块名校验

需要通过组件实例调用 $registerModule ,因为在自动注销模块是需要使用组件实例

function registerModule(moduleName, moduleStore, options = {}) {
    if (!(this instanceof Vue)) {
        throw new Error('请在组件内使用this.$registerModule')
    }
    const moduleRegexp = new RegExp(/Module$/)
    if (!moduleRegexp.test(moduleName)) {
        throw new Error(`${moduleName} 模块命名请以Module结尾`)
    }
    ...
}

模块重复注册

vuex本身带有检测模块是否注册接口hasModule,但却是从3.2.0版本开始支持,这里需要向下兼容

function registerModule(moduleName, moduleStore, options = {}) {
    ...
    // vuex自带支持 hasModule 的API从3.2.0新增
    // https://vuex.vuejs.org/zh/api/#hasmodule
    const moduleNames = []
    let hasModule = false
    if (store.hasModule) {
        hasModule = store.hasModule(moduleName)
    } else {
        hasModule = moduleNames.includes(moduleName)
    }
    if (hasModule) {
        console.warn(`模块${moduleName}已经注册`)
    } else {
        // 注册模块
        store.registerModule(moduleName, moduleStore)
    }
    ...
}

自动注销模块

默认在业务场景离开后会自动注销store子模块,如果需要持久化的话,设置 autoUnregister

  • 借助Vue中组件在执行生命周期钩子函数时,会调用相应的 hook: 事件,通过监听 hook:beforeDestroy 事件,实现自动注销子模块
  • 在模块注销时,需要将依赖全部清除才可以注销,详情请查看
function registerModule(moduleName, moduleStore, options = {}) {
    ...
    // 自动注销模块,如果需要store持久化,传入
    const {autoUnregister = true} = options
    if (autoUnregister) {
        moduleNames.push(moduleName)
        this.$on('hook:beforeDestroy', unregisterModule.bind(this, moduleName))
    }
}

function unregisterModule(moduleName) {
    // 清除moduleName
    const idx = moduleNames.indexOf(moduleName)
    moduleNames.splice(idx, 1)
    // 需要完全清除依赖才注销
    // https://github.com/XyyF/vuex-register-module-demo/issues/1
    if (!moduleNames.includes(moduleName)) {
        store.unregisterModule(moduleName)
    }
}

完整代码

demo online
demo github

const moduleNames = []

export default {
    install(Vue, options) {
        const {store} = options
        // 添加实例上的引用
        Vue.prototype.$registerModule = registerModule

        function registerModule(moduleName, moduleStore, options = {}) {
            if (!(this instanceof Vue)) {
                throw new Error('请在组件内使用this.$registerModule')
            }

            const moduleRegexp = new RegExp(/Module$/)

            if (!moduleRegexp.test(moduleName)) {
                throw new Error(`${moduleName} 模块命名请以Module结尾`)
            }
            // vuex自带支持 hasModule 的API从3.2.0新增
            // https://vuex.vuejs.org/zh/api/#hasmodule
            let hasModule = false
            if (store.hasModule) {
                hasModule = store.hasModule(moduleName)
            } else {
                hasModule = moduleNames.includes(moduleName)
            }
            if (hasModule) {
                console.warn(`模块${moduleName}已经注册`)
            } else {
                // 注册模块
                store.registerModule(moduleName, moduleStore)
            }
            // 自动注销模块,如果需要store持久化,传入
            const {autoUnregister = true} = options
            if (autoUnregister) {
                moduleNames.push(moduleName)
                this.$on('hook:beforeDestroy', unregisterModule.bind(this, moduleName))
            }
        }

        function unregisterModule(moduleName) {
            // 清除moduleName
            const idx = moduleNames.indexOf(moduleName)
            moduleNames.splice(idx, 1)
            // 需要完全清除依赖才注销
            // https://github.com/XyyF/vuex-register-module-demo/issues/1
            if (!moduleNames.includes(moduleName)) {
                store.unregisterModule(moduleName)
            }
        }
    },
}