二次封装 Vuex for Uniapp 微信小程序开发

作为高级前端开发工程师,我将为你提供一个针对 Uniapp + Vue2 + Vuex 的 Store 二次封装方案,使团队成员能够更便捷地使用和管理状态。

封装目标

  1. 模块化管理状态

  2. 简化调用方式

  3. 提供类型提示(在 Vue2 中尽可能实现)

  4. 便于维护和查找

封装方案

1. 目录结构重构

首先,我们重构 store 的目录结构:

store/

├── index.js # 主入口文件

├── modules/ # 模块目录

│ ├── user.js # 用户模块

│ ├── cart.js # 购物车模块

│ └── ... # 其他模块

└── store-utils.js # 封装的工具方法

2. 主入口文件封装 (store/index.js)

javascript 复制代码
import Vue from 'vue'
import Vuex from 'vuex'
import * as utils from './store-utils'

Vue.use(Vuex)

// 自动加载 modules 目录下的所有模块
const modules = utils.autoLoadModules()

const store = new Vuex.Store({
  modules,
  // 全局配置
  strict: process.env.NODE_ENV !== 'production'
})

// 挂载工具方法到 Vue 原型
Vue.prototype.$storeUtils = utils

export default store

3. 工具方法封装 (store/store-utils.js)

javascript 复制代码
/**
 * 自动加载 modules 目录下的所有模块
 */
export function autoLoadModules() {
  const modules = {}
  const requireModule = require.context(
    // 搜索目录
    './modules',
    // 是否搜索子目录
    false,
    // 匹配文件的正则
    /\.js$/
  )
  
  requireModule.keys().forEach(fileName => {
    // 获取模块名(去掉文件扩展名)
    const moduleName = fileName.replace(/(\.\/|\.js)/g, '')
    modules[moduleName] = {
      namespaced: true,
      ...requireModule(fileName).default
    }
  })
  
  return modules
}

/**
 * 简化 mapState 调用
 * @param {string} moduleName 模块名
 * @param {Array|Object} states 状态名数组或对象
 */
export function mapState(moduleName, states) {
  if (!moduleName) return {}
  
  if (Array.isArray(states)) {
    const result = {}
    states.forEach(key => {
      result[key] = function() {
        return this.$store.state[moduleName][key]
      }
    })
    return result
  }
  
  // 对象形式
  const result = {}
  Object.keys(states).forEach(key => {
    result[key] = function() {
      return this.$store.state[moduleName][states[key]]
    }
  })
  return result
}

/**
 * 简化 mapGetters 调用
 * @param {string} moduleName 模块名
 * @param {Array|Object} getters getter名数组或对象
 */
export function mapGetters(moduleName, getters) {
  if (!moduleName) return {}
  
  const namespace = moduleName + '/'
  
  if (Array.isArray(getters)) {
    const result = {}
    getters.forEach(getterName => {
      result[getterName] = function() {
        return this.$store.getters[namespace + getterName]
      }
    })
    return result
  }
  
  // 对象形式
  const result = {}
  Object.keys(getters).forEach(key => {
    result[key] = function() {
      return this.$store.getters[namespace + getters[key]]
    }
  })
  return result
}

/**
 * 简化 mapMutations 调用
 * @param {string} moduleName 模块名
 * @param {Array|Object} mutations mutation名数组或对象
 */
export function mapMutations(moduleName, mutations) {
  if (!moduleName) return {}
  
  const namespace = moduleName + '/'
  
  if (Array.isArray(mutations)) {
    const result = {}
    mutations.forEach(mutationName => {
      result[mutationName] = function(payload) {
        this.$store.commit(namespace + mutationName, payload)
      }
    })
    return result
  }
  
  // 对象形式
  const result = {}
  Object.keys(mutations).forEach(key => {
    result[key] = function(payload) {
      this.$store.commit(namespace + mutations[key], payload)
    }
  })
  return result
}

/**
 * 简化 mapActions 调用
 * @param {string} moduleName 模块名
 * @param {Array|Object} actions action名数组或对象
 */
export function mapActions(moduleName, actions) {
  if (!moduleName) return {}
  
  const namespace = moduleName + '/'
  
  if (Array.isArray(actions)) {
    const result = {}
    actions.forEach(actionName => {
      result[actionName] = function(payload) {
        return this.$store.dispatch(namespace + actionName, payload)
      }
    })
    return result
  }
  
  // 对象形式
  const result = {}
  Object.keys(actions).forEach(key => {
    result[key] = function(payload) {
      return this.$store.dispatch(namespace + actions[key], payload)
    }
  })
  return result
}

/**
 * 创建模块的快捷访问方式
 * @param {string} moduleName 模块名
 */
export function createModuleHelpers(moduleName) {
  return {
    state: (stateName) => {
      return this.$store.state[moduleName][stateName]
    },
    getter: (getterName) => {
      return this.$store.getters[`${moduleName}/${getterName}`]
    },
    commit: (mutationName, payload) => {
      this.$store.commit(`${moduleName}/${mutationName}`, payload)
    },
    dispatch: (actionName, payload) => {
      return this.$store.dispatch(`${moduleName}/${actionName}`, payload)
    }
  }
}

4. 模块示例 (store/modules/user.js)

javascript 复制代码
const state = {
  token: '',
  userInfo: null,
  isLogin: false
}

const getters = {
  getUserName: state => {
    return state.userInfo?.name || ''
  },
  getUserId: state => {
    return state.userInfo?.id || ''
  }
}

const mutations = {
  SET_TOKEN(state, token) {
    state.token = token
  },
  SET_USER_INFO(state, userInfo) {
    state.userInfo = userInfo
    state.isLogin = !!userInfo
  },
  CLEAR_USER(state) {
    state.token = ''
    state.userInfo = null
    state.isLogin = false
  }
}

const actions = {
  login({ commit }, { username, password }) {
    return new Promise((resolve, reject) => {
      // 模拟登录请求
      uni.request({
        url: '/api/login',
        method: 'POST',
        data: { username, password },
        success: (res) => {
          commit('SET_TOKEN', res.data.token)
          commit('SET_USER_INFO', res.data.userInfo)
          resolve(res.data)
        },
        fail: (err) => {
          reject(err)
        }
      })
    })
  },
  logout({ commit }) {
    commit('CLEAR_USER')
    return Promise.resolve()
  }
}

export default {
  state,
  getters,
  mutations,
  actions
}

使用方式

1. 组件中使用

javascript 复制代码
import { mapState, mapGetters, mapMutations, mapActions } from '@/store/store-utils'

export default {
  computed: {
    // 传统方式
    ...mapState('user', ['token', 'userInfo', 'isLogin']),
    ...mapGetters('user', ['getUserName', 'getUserId']),
    
    // 或者使用对象形式
    ...mapState('user', {
      myToken: 'token',
      myUserInfo: 'userInfo'
    })
  },
  methods: {
    ...mapMutations('user', ['SET_USER_INFO']),
    ...mapActions('user', ['login', 'logout']),
    
    // 示例方法
    handleLogin() {
      this.login({ username: 'test', password: '123456' })
        .then(() => {
          uni.showToast({ title: '登录成功' })
        })
        .catch(err => {
          uni.showToast({ title: '登录失败', icon: 'none' })
        })
    }
  }
}

2. 快捷访问方式

javascript 复制代码
export default {
  methods: {
    someMethod() {
      // 使用快捷访问
      const userHelpers = this.$storeUtils.createModuleHelpers('user')
      
      // 获取状态
      const token = userHelpers.state('token')
      
      // 获取getter
      const userName = userHelpers.getter('getUserName')
      
      // 提交mutation
      userHelpers.commit('SET_USER_INFO', { name: '张三' })
      
      // 分发action
      userHelpers.dispatch('logout').then(() => {
        console.log('登出成功')
      })
    }
  }
}

3. 在JS文件中使用

javascript 复制代码
import store from '@/store'

// 直接使用store
const token = store.state.user.token

// 提交mutation
store.commit('user/SET_TOKEN', 'new-token')

// 分发action
store.dispatch('user/login', { username: 'test', password: '123456' })
  .then(() => {
    console.log('登录成功')
  })

最佳实践建议

  1. 命名规范

    • 模块名使用小驼峰命名法 (user, shoppingCart)

    • state 属性使用小驼峰命名法

    • mutations 使用大写蛇形命名法 (SET_USER_INFO)

    • actions 使用小驼峰命名法

  2. 文档注释

    在每个模块文件顶部添加注释说明模块用途,重要的 state、getter、mutation 和 action 添加注释

  3. 模块拆分

    • 按业务功能拆分模块

    • 避免单个模块过大

相关推荐
疯狂的沙粒3 小时前
uniapp 开发企业微信小程序时,如何在当前页面真正销毁前或者关闭小程序前调用一个api接口
微信小程序·小程序·uni-app
山河故人1633 小时前
UniApp微信小程序自定义导航栏实现
微信小程序·uni-app·notepad++
魔术师ID4 小时前
微信小程序学习目录
学习·微信小程序·小程序
一蓑烟雨,一任平生10 小时前
在h5端实现录音发送功能(兼容内嵌微信小程序) recorder-core
微信小程序·小程序
lqj_本人12 小时前
鸿蒙OS&UniApp 制作倒计时与提醒功能#三方框架 #Uniapp
华为·uni-app·harmonyos
说私域12 小时前
多级体验体系构建:基于开源AI智能客服与AI智能名片的S2B2C商城小程序体验升级路径研究
人工智能·小程序·开源·零售
靓仔建13 小时前
uniapp调用java接口 跨域问题
java·开发语言·uni-app
^Rocky14 小时前
微信小程序(uniapp)对接腾讯云IM
微信小程序·uni-app·腾讯云
栈狮14 小时前
Uniapp+UView+Uni-star打包小程序极简方案
小程序·uni-app
moxiaoran575317 小时前
uni-app学习笔记十五-vue3页面生命周期(二)
笔记·学习·uni-app